Skip to content

Commit f6e086c

Browse files
committed
improve forgotten kingdom detection logic
1 parent 251acaa commit f6e086c

2 files changed

Lines changed: 250 additions & 33 deletions

File tree

core/src/main/java/de/sesu8642/feudaltactics/lib/gamestate/GameStateHelper.java

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,48 +1006,62 @@ public static int getKingdomSalaries(GameState gameState, Kingdom kingdom) {
10061006
public static Optional<Kingdom> getFirstForgottenKingdom(GameState gameState) {
10071007
for (Kingdom kingdom : gameState.getKingdoms()) {
10081008
if (kingdom.getPlayer() == gameState.getActivePlayer() && !kingdom.isWasActiveInCurrentTurn()) {
1009-
// can afford castle
1010-
if (kingdom.getSavings() >= Castle.COST) {
1011-
return Optional.of(kingdom);
1012-
}
1013-
boolean hasPeasant = false;
1014-
boolean hasTree = false;
1009+
boolean hasRemovableTileContent = false;
1010+
boolean hasEmptyTile = false;
1011+
int sumOfUnitStrength = 0;
10151012
for (HexTile tile : kingdom.getTiles()) {
1016-
if (tile.getContent() != null
1017-
&& ClassReflection.isAssignableFrom(Unit.class, tile.getContent().getClass())) {
1018-
if (tile.getContent().getStrength() > 1) {
1019-
// has unit stronger than peasant
1020-
return Optional.of(kingdom);
1021-
} else if (((Unit) tile.getContent()).getUnitType() == UnitTypes.PEASANT) {
1022-
hasPeasant = true;
1023-
}
1024-
} else if (tile.getContent() != null
1025-
&& (ClassReflection.isAssignableFrom(Tree.class, tile.getContent().getClass())
1026-
|| ClassReflection.isAssignableFrom(PalmTree.class, tile.getContent().getClass()))) {
1027-
hasTree = true;
1013+
if (tile.getContent() == null) {
1014+
hasEmptyTile = true;
1015+
} else if (ClassReflection.isAssignableFrom(Unit.class, tile.getContent().getClass())) {
1016+
sumOfUnitStrength += tile.getContent().getStrength();
1017+
} else if (!hasRemovableTileContent && (ClassReflection.isAssignableFrom(Tree.class,
1018+
tile.getContent().getClass())
1019+
|| ClassReflection.isAssignableFrom(PalmTree.class, tile.getContent().getClass())
1020+
|| ClassReflection.isAssignableFrom(Gravestone.class, tile.getContent().getClass()))) {
1021+
hasRemovableTileContent = true;
10281022
}
10291023
}
1030-
final boolean canBuyPeasant = kingdom.getSavings() >= Unit.COST;
1031-
// has or can get peasant that can conquer something or destroy tree
1032-
if (hasPeasant || canBuyPeasant) {
1033-
if (hasTree) {
1034-
return Optional.of(kingdom);
1035-
}
1036-
// there is a neighbor tile which can be conquered by the peasant
1037-
for (HexTile tile : kingdom.getTiles()) {
1038-
for (HexTile neighborTile : HexMapHelper.getNeighborTiles(gameState.getMap(), tile)) {
1039-
if (neighborTile != null && neighborTile.getKingdom() != tile.getKingdom()
1040-
&& getProtectionLevel(gameState, neighborTile) == 0) {
1041-
return Optional.of(kingdom);
1042-
}
1043-
}
1044-
}
1024+
final int numberOfPeasantsThatCanBeAfforded = kingdom.getSavings() / Unit.COST;
1025+
int sumOfTotalPossibleUnitStrength =
1026+
sumOfUnitStrength + (numberOfPeasantsThatCanBeAfforded * UnitTypes.PEASANT.strength());
1027+
// cannot practically make a stronger unit than baron
1028+
sumOfTotalPossibleUnitStrength = Math.min(sumOfTotalPossibleUnitStrength,
1029+
UnitTypes.strongest().strength());
1030+
final boolean canAffordCastle = kingdom.getSavings() >= Castle.COST;
1031+
if (wasKingdomPotentiallyForgotten(gameState, kingdom, canAffordCastle, hasEmptyTile,
1032+
hasRemovableTileContent,
1033+
sumOfTotalPossibleUnitStrength)) {
1034+
return Optional.of(kingdom);
10451035
}
10461036
}
10471037
}
10481038
return Optional.empty();
10491039
}
10501040

1041+
private static boolean wasKingdomPotentiallyForgotten(GameState gameState, Kingdom kingdom, boolean canAffordCastle,
1042+
boolean hasEmptyTile, boolean hasTree,
1043+
int sumOfTotalPossibleUnitStrength) {
1044+
if (canAffordCastle && hasEmptyTile) {
1045+
return true;
1046+
}
1047+
// has or can get unit that can conquer something or destroy tree
1048+
if (sumOfTotalPossibleUnitStrength > 0) {
1049+
if (hasTree) {
1050+
return true;
1051+
}
1052+
// there is a neighbor tile which can be conquered by some unit
1053+
for (HexTile tile : kingdom.getTiles()) {
1054+
for (HexTile neighborTile : HexMapHelper.getNeighborTiles(gameState.getMap(), tile)) {
1055+
if (neighborTile != null && neighborTile.getKingdom() != tile.getKingdom()
1056+
&& getProtectionLevel(gameState, neighborTile) < sumOfTotalPossibleUnitStrength) {
1057+
return true;
1058+
}
1059+
}
1060+
}
1061+
}
1062+
return false;
1063+
}
1064+
10511065
/**
10521066
* Determines which local player is responsible for any inputs.
10531067
*
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
package de.sesu8642.feudaltactics.lib.gamestate;
4+
5+
import com.badlogic.gdx.math.Vector2;
6+
import com.badlogic.gdx.utils.reflect.ClassReflection;
7+
import com.badlogic.gdx.utils.reflect.ReflectionException;
8+
import com.google.common.collect.ImmutableList;
9+
import de.sesu8642.feudaltactics.lib.gamestate.Player.Type;
10+
import org.junit.jupiter.api.Test;
11+
import org.junit.jupiter.params.ParameterizedTest;
12+
import org.junit.jupiter.params.provider.ValueSource;
13+
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
import java.util.Optional;
17+
18+
import static de.sesu8642.feudaltactics.lib.gamestate.Unit.UnitTypes.*;
19+
import static org.junit.jupiter.api.Assertions.assertFalse;
20+
import static org.junit.jupiter.api.Assertions.assertTrue;
21+
22+
/**
23+
* Tests for GameStateHelper class related to detecting potentially forgotten kingdoms.
24+
*/
25+
class GameStateHelperForgottenKingdomTest {
26+
27+
private static GameState createBaseGameState() {
28+
final GameState gameState = new GameState();
29+
final Player player1 = new Player(0, Type.LOCAL_BOT);
30+
final Player player2 = new Player(1, Type.LOCAL_PLAYER);
31+
final List<Player> players = new ArrayList<>(ImmutableList.of(player1, player2));
32+
GameStateHelper.initializeMap(gameState, players, 5, 10F, 0F, 1L);
33+
return gameState;
34+
}
35+
36+
@Test
37+
void previouslyActiveKingdom_isNotForgotten() {
38+
final GameState gameState = createBaseGameState();
39+
gameState.getMap().get(new Vector2(0, -2)).getKingdom().setWasActiveInCurrentTurn(true);
40+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
41+
assertFalse(forgottenKingdom.isPresent());
42+
}
43+
44+
@Test
45+
void unprotectedTileButCanAffordPeasant_isForgotten() {
46+
final GameState gameState = createBaseGameState();
47+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
48+
assertTrue(forgottenKingdom.isPresent());
49+
}
50+
51+
@Test
52+
void unprotectedTileButNoUnitOrEnoughMoney_isNotForgotten() {
53+
final GameState gameState = createBaseGameState();
54+
gameState.getMap().get(new Vector2(0, -2)).getKingdom().setSavings(9);
55+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
56+
assertFalse(forgottenKingdom.isPresent());
57+
}
58+
59+
@Test
60+
void strength1ProtectedTileButCanAffordOnlyPeasant_isNotForgotten() {
61+
final GameState gameState = createBaseGameState();
62+
gameState.getMap().get(new Vector2(1, -1)).setContent(new Unit(PEASANT));
63+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
64+
assertFalse(forgottenKingdom.isPresent());
65+
}
66+
67+
@Test
68+
void strength1ProtectedTileButCanAffordSpearman_isForgotten() {
69+
final GameState gameState = createBaseGameState();
70+
gameState.getMap().get(new Vector2(1, -1)).setContent(new Unit(PEASANT));
71+
gameState.getMap().get(new Vector2(0, -2)).getKingdom().setSavings(20);
72+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
73+
assertTrue(forgottenKingdom.isPresent());
74+
}
75+
76+
@Test
77+
void strength1ProtectedTileButCanCombineSpearman_isForgotten() {
78+
final GameState gameState = createBaseGameState();
79+
gameState.getMap().get(new Vector2(1, -1)).setContent(new Unit(PEASANT));
80+
gameState.getMap().get(new Vector2(0, -2)).setContent(new Unit(PEASANT));
81+
gameState.getMap().get(new Vector2(0, -2)).getKingdom().setSavings(10);
82+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
83+
assertTrue(forgottenKingdom.isPresent());
84+
}
85+
86+
@Test
87+
void strength2ProtectedTileButCanCombineSpearman_isNotForgotten() {
88+
final GameState gameState = createBaseGameState();
89+
gameState.getMap().get(new Vector2(1, -1)).setContent(new Castle());
90+
gameState.getMap().get(new Vector2(0, -2)).setContent(new Unit(PEASANT));
91+
gameState.getMap().get(new Vector2(0, -2)).getKingdom().setSavings(10);
92+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
93+
assertFalse(forgottenKingdom.isPresent());
94+
}
95+
96+
@Test
97+
void strength2ProtectedTileButCanCombineKnight_isForgotten() {
98+
final GameState gameState = createBaseGameState();
99+
gameState.getMap().get(new Vector2(1, -1)).setContent(new Castle());
100+
gameState.getMap().get(new Vector2(0, -2)).setContent(new Unit(PEASANT));
101+
gameState.getMap().get(new Vector2(0, -2)).getKingdom().setSavings(20);
102+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
103+
assertTrue(forgottenKingdom.isPresent());
104+
}
105+
106+
@Test
107+
void strength2ProtectedTileButCanAffordKnight_isForgotten() {
108+
final GameState gameState = createBaseGameState();
109+
gameState.getMap().get(new Vector2(1, -1)).setContent(new Castle());
110+
gameState.getMap().get(new Vector2(0, -2)).getKingdom().setSavings(30);
111+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
112+
assertTrue(forgottenKingdom.isPresent());
113+
}
114+
115+
@Test
116+
void strength3ProtectedTileButCanAffordKnight_isForgotten() {
117+
final GameState gameState = createBaseGameState();
118+
gameState.getMap().get(new Vector2(1, -1)).setContent(new Unit(KNIGHT));
119+
gameState.getMap().get(new Vector2(0, -2)).getKingdom().setSavings(30);
120+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
121+
assertTrue(forgottenKingdom.isPresent());
122+
}
123+
124+
@Test
125+
void strength3ProtectedTileButCanAffordBaron_isForgotten() {
126+
final GameState gameState = createBaseGameState();
127+
gameState.getMap().get(new Vector2(1, -1)).setContent(new Unit(KNIGHT));
128+
gameState.getMap().get(new Vector2(0, -2)).getKingdom().setSavings(40);
129+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
130+
assertTrue(forgottenKingdom.isPresent());
131+
}
132+
133+
@Test
134+
void strength3ProtectedTileButCanCombineBaron_isForgotten() {
135+
final GameState gameState = createBaseGameState();
136+
gameState.getMap().get(new Vector2(1, -1)).setContent(new Unit(KNIGHT));
137+
gameState.getMap().get(new Vector2(0, -2)).setContent(new Unit(SPEARMAN));
138+
gameState.getMap().get(new Vector2(0, -2)).getKingdom().setSavings(20);
139+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
140+
assertTrue(forgottenKingdom.isPresent());
141+
}
142+
143+
@Test
144+
void strength4ProtectedTileButIsRich_isNotForgotten() {
145+
final GameState gameState = createBaseGameState();
146+
gameState.getMap().get(new Vector2(1, -1)).setContent(new Unit(BARON));
147+
gameState.getMap().get(new Vector2(0, -2)).setContent(new Unit(BARON));
148+
gameState.getMap().get(new Vector2(0, -2)).getKingdom().setSavings(1000);
149+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
150+
assertFalse(forgottenKingdom.isPresent());
151+
}
152+
153+
@ParameterizedTest
154+
@ValueSource(classes = {Tree.class, PalmTree.class, Gravestone.class})
155+
void blockingObjectButCannotAffordUnit_isNotForgotten(Class<TileContent> tileContentClass) throws ReflectionException {
156+
final GameState gameState = createBaseGameState();
157+
gameState.getMap().get(new Vector2(1, -1)).setContent(new Castle());
158+
gameState.getMap().get(new Vector2(0, -2)).setContent(ClassReflection.newInstance(tileContentClass));
159+
gameState.getMap().get(new Vector2(0, -2)).getKingdom().setSavings(9);
160+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
161+
assertFalse(forgottenKingdom.isPresent());
162+
}
163+
164+
@ParameterizedTest
165+
@ValueSource(classes = {Tree.class, PalmTree.class, Gravestone.class})
166+
void blockingObjectButCanAffordPeasant_isForgotten(Class<TileContent> tileContentClass) throws ReflectionException {
167+
final GameState gameState = createBaseGameState();
168+
gameState.getMap().get(new Vector2(1, -1)).setContent(new Castle());
169+
gameState.getMap().get(new Vector2(0, -2)).setContent(ClassReflection.newInstance(tileContentClass));
170+
gameState.getMap().get(new Vector2(0, -2)).getKingdom().setSavings(10);
171+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
172+
assertTrue(forgottenKingdom.isPresent());
173+
}
174+
175+
@Test
176+
void emptyTileButCanAffordCastle_isForgotten() {
177+
final GameState gameState = createBaseGameState();
178+
gameState.getMap().get(new Vector2(1, -1)).setContent(new Castle());
179+
gameState.getMap().get(new Vector2(0, -2)).getKingdom().setSavings(15);
180+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
181+
assertTrue(forgottenKingdom.isPresent());
182+
}
183+
184+
@Test
185+
void emptyTileButCanNotAffordCastle_isNotForgotten() {
186+
final GameState gameState = createBaseGameState();
187+
gameState.getMap().get(new Vector2(1, -1)).setContent(new Castle());
188+
gameState.getMap().get(new Vector2(0, -2)).getKingdom().setSavings(14);
189+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
190+
assertFalse(forgottenKingdom.isPresent());
191+
}
192+
193+
@Test
194+
void noEmptyTileButCanAffordCastle_isNotForgotten() {
195+
final GameState gameState = createBaseGameState();
196+
gameState.getMap().get(new Vector2(1, -1)).setContent(new Castle());
197+
gameState.getMap().get(new Vector2(0, -2)).setContent(new Castle());
198+
gameState.getMap().get(new Vector2(0, -2)).getKingdom().setSavings(15);
199+
final Optional<Kingdom> forgottenKingdom = GameStateHelper.getFirstForgottenKingdom(gameState);
200+
assertFalse(forgottenKingdom.isPresent());
201+
}
202+
203+
}

0 commit comments

Comments
 (0)