Skip to content

Commit a7826b1

Browse files
committed
1. Bug fix where an ocean tile cannot be assigned if the city is at expansion level greater than 2.
2. Added more conditions to how a tile ownership is determined, and how/if unassigned tiles are correctly assigned to a city.
1 parent 5a2a868 commit a7826b1

File tree

4 files changed

+95
-19
lines changed

4 files changed

+95
-19
lines changed

C7/MapView.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ public override void drawObject(LooseView looseView, GameData gameData, Tile til
271271
int randomJungleRow = tile.YCoordinate % 2;
272272
int randomJungleColumn;
273273
ImageTexture jungleTexture;
274-
if (tile.getEdgeNeighbors().Any(t => t.IsWater())) {
274+
if (tile.GetEdgeNeighbors().Any(t => t.IsWater())) {
275275
randomJungleColumn = tile.XCoordinate % 6;
276276
jungleTexture = smallJungleTexture;
277277
} else {
@@ -298,7 +298,7 @@ public override void drawObject(LooseView looseView, GameData gameData, Tile til
298298
}
299299
} else {
300300
forestRow = tile.YCoordinate % 2;
301-
if (tile.getEdgeNeighbors().Any(t => t.IsWater())) {
301+
if (tile.GetEdgeNeighbors().Any(t => t.IsWater())) {
302302
forestColumn = tile.XCoordinate % 5;
303303
if (tile.baseTerrainType.Key == "grassland") {
304304
forestTexture = smallForestTexture;
@@ -343,7 +343,7 @@ public override void drawObject(LooseView looseView, GameData gameData, Tile til
343343
int randomJungleRow = tile.YCoordinate % 2;
344344
int randomMarshColumn;
345345
ImageTexture marshTexture;
346-
if (tile.getEdgeNeighbors().Any(t => t.IsWater())) {
346+
if (tile.GetEdgeNeighbors().Any(t => t.IsWater())) {
347347
randomMarshColumn = tile.XCoordinate % 5;
348348
marshTexture = smallMarshTexture;
349349
} else {

C7Engine/C7GameData/City.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,8 +670,9 @@ public List<Tile> GetTilesWithinBorders() {
670670
private List<Tile> GetTilesOfRank(int rank) {
671671
List<Tile> result = new();
672672
foreach (Tile t in location.GetTilesWithinRankDistance(rank)) {
673+
// Law II
673674
// Ocean tiles may only hold claims of rank 2.
674-
if (t.baseTerrainType.Key == "ocean" && rank > 2) {
675+
if (t.baseTerrainType.Key == "ocean" && t.rankDistanceTo(location) > 2) {
675676
continue;
676677
}
677678
result.Add(t);

C7Engine/C7GameData/GameData.cs

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,26 @@ public void UpdateTileOwners() {
147147
foreach (Player player in players) {
148148
player.tileKnowledge.RecomputeActiveTiles();
149149
player.UpdateResourcesInBorders(map.tiles.Where(t => t.owningCity?.owner == player));
150+
151+
foreach (Tile t in player.tileKnowledge.knownTiles.Where(t => t.owningCity == null && t.GetEdgeNeighbors().Any(e => e.owningCity != null)).ToList()) {
152+
// Law VII
153+
if (t.neighbors.TryGetValue(TileDirection.NORTHWEST, out Tile tnw)
154+
&& t.neighbors.TryGetValue(TileDirection.SOUTHEAST, out Tile tse)
155+
&& tnw.owningCity != null && tse.owningCity != null
156+
&& tnw.owningCity.owner == tse.owningCity.owner) {
157+
t.owningCity = ResolveTileOwnershipConflict(tnw.owningCity, tse.owningCity, t);
158+
t.owningCity.owner.tileKnowledge.AddTilesToKnown(t);
159+
continue;
160+
}
161+
// Law VIII
162+
if (t.neighbors.TryGetValue(TileDirection.NORTHEAST, out Tile tne)
163+
&& t.neighbors.TryGetValue(TileDirection.SOUTHWEST, out Tile tsw)
164+
&& tne.owningCity != null && tsw.owningCity != null
165+
&& tne.owningCity.owner == tsw.owningCity.owner) {
166+
t.owningCity = ResolveTileOwnershipConflict(tne.owningCity, tsw.owningCity, t);
167+
t.owningCity.owner.tileKnowledge.AddTilesToKnown(t);
168+
}
169+
}
150170
}
151171
}
152172

@@ -256,26 +276,46 @@ public void InvalidateCachedTradeNetwork() {
256276

257277
// Rules taken from https://forums.civfanatics.com/threads/the-eight-laws-of-border-dynamics.106882/
258278
private City ResolveTileOwnershipConflict(City a, City b, Tile t) {
279+
if (a.Equals(b)) return a;
280+
259281
int aRank = a.location.rankDistanceTo(t);
260282
int bRank = b.location.rankDistanceTo(t);
261283

284+
// Law I
285+
// Cities can claim tiles of rank n+1, where n is the city's expansion level
286+
if (a.GetBorderExpansionLevel() + 1 < aRank && b.GetBorderExpansionLevel() + 1 >= bRank) return b;
287+
if (b.GetBorderExpansionLevel() + 1 < bRank && a.GetBorderExpansionLevel() + 1 >= aRank) return a;
288+
289+
// Law III
262290
// The city with the lowest rank claim gets the tile.
263-
if (aRank > bRank) {
264-
return b;
265-
} else if (aRank < bRank) {
266-
return a;
267-
}
291+
if (aRank > bRank) return b;
292+
if (aRank < bRank) return a;
268293

294+
// Law IV
269295
// If the ranks are equal, the city with more culture gets the tile.
270-
if (a.GetCulture() < b.GetCulture()) {
271-
return b;
272-
} else if (a.GetCulture() > b.GetCulture()) {
273-
return a;
274-
}
296+
if (a.GetCulture() + a.GetCulturePerTurn() < b.GetCulture() + b.GetCulturePerTurn()) return b;
297+
if (a.GetCulture() + a.GetCulturePerTurn() > b.GetCulture() + b.GetCulturePerTurn()) return a;
275298

299+
// Law V
276300
// If the cultures are equal the oldest city gets the tile.
277-
// TODO: track city age - for now we just return the first.
278-
return a;
301+
// TODO: track city age - for now we are going to skip this.
302+
// return a;
303+
304+
// Law VI
305+
// Starting North of the disputed tile, we go counter-clockwise
306+
// trying to find the first tile that has one of the competing cities.
307+
// We start at (rank - 1) because the rank distance does not necessarily reflect the actual "ring"
308+
// the city tile is in, so a tile at rank 3, could well mean it's in the 2nd ring.
309+
for (int r = aRank - 1; r <= aRank; r++) {
310+
if (r <= 0) continue;
311+
Tile winner = t.FindInRing(r, tile => tile.HasCity && (tile.cityAtTile == a || tile.cityAtTile == b), false);
312+
if (winner != null) {
313+
return winner.owningCity;
314+
}
315+
}
316+
317+
// should never happen, if it does some part of the algorithm has gone wrong
318+
throw new Exception($"Failed to resolve ownership of {t} between {a.name} and {b.name}, something went wrong");
279319
}
280320
}
281321
}

C7Engine/C7GameData/Tile.cs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,13 @@ public bool NeighborsOcean() {
186186
/// This is used by some graphics algorithms.
187187
/// </summary>
188188
/// <returns></returns>
189-
public Tile[] getEdgeNeighbors() {
190-
Tile[] edgeNeighbors = { neighbors[TileDirection.NORTHEAST], neighbors[TileDirection.NORTHWEST], neighbors[TileDirection.SOUTHEAST], neighbors[TileDirection.SOUTHWEST]};
191-
return edgeNeighbors;
189+
public Tile[] GetEdgeNeighbors() {
190+
List<Tile> edgeNeighbors = new();
191+
if (neighbors.TryGetValue(TileDirection.NORTHEAST, out Tile ne)) edgeNeighbors.Add(ne);
192+
if (neighbors.TryGetValue(TileDirection.NORTHWEST, out Tile nw)) edgeNeighbors.Add(nw);
193+
if (neighbors.TryGetValue(TileDirection.SOUTHEAST, out Tile se)) edgeNeighbors.Add(se);
194+
if (neighbors.TryGetValue(TileDirection.SOUTHWEST, out Tile sw)) edgeNeighbors.Add(sw);
195+
return edgeNeighbors.ToArray();
192196
}
193197

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

619+
public Tile FindInRing(int rank, Func<Tile, bool> predicate, bool clockwise = true) {
620+
Tile startingTile = map.tileAt(this.XCoordinate, this.YCoordinate - (2 * rank));
621+
622+
if (predicate(startingTile)) return startingTile;
623+
624+
int dx = startingTile.XCoordinate;
625+
int dy = startingTile.YCoordinate;
626+
627+
// Going SW(counter-clockwise) or SE(clockwise)
628+
for (int _ = 1; _ < (2 * rank) + 1; _++) {
629+
if (clockwise) { dx++; dy++; } else { dx--; dy++; }
630+
if (predicate(map.tileAt(dx, dy))) return map.tileAt(dx, dy);
631+
}
632+
// Going SE(counter-clockwise) or SW(clockwise)
633+
for (int _ = 1; _ < (2 * rank) + 1; _++) {
634+
if (clockwise) { dx--; dy++; } else { dx++; dy++; }
635+
if (predicate(map.tileAt(dx, dy))) return map.tileAt(dx, dy);
636+
}
637+
// Going NE(counter-clockwise) or NW(clockwise)
638+
for (int _ = 1; _ < (2 * rank) + 1; _++) {
639+
if (clockwise) { dx--; dy--; } else { dx++; dy--; }
640+
if (predicate(map.tileAt(dx, dy))) return map.tileAt(dx, dy);
641+
}
642+
// Going NW(counter-clockwise) or NE(clockwise)
643+
for (int _ = 1; _ < (2 * rank); _++) {
644+
if (clockwise) { dx++; dy--; } else { dx--; dy--; }
645+
if (predicate(map.tileAt(dx, dy))) return map.tileAt(dx, dy);
646+
}
647+
return null;
648+
}
649+
615650
// Returns the tiles in the spiral ordering defined by
616651
// GetTileAtNeighborIndex(i).
617652
public List<Tile> GetTilesWithinRankDistance(int rank) {

0 commit comments

Comments
 (0)