| 1 | package de.aikiit.game.kaiser; | |
| 2 | ||
| 3 | import lombok.Getter; | |
| 4 | import org.assertj.core.util.VisibleForTesting; | |
| 5 | ||
| 6 | import java.math.BigDecimal; | |
| 7 | import java.math.RoundingMode; | |
| 8 | import java.security.SecureRandom; | |
| 9 | import java.util.Random; | |
| 10 | ||
| 11 | import static org.apache.commons.lang3.compare.ComparableUtils.is; | |
| 12 | ||
| 13 | /** | |
| 14 | * This class encapsulates the game's logics and operations on its attributes. | |
| 15 | * As the game is based on rounds {@link #startNewRound()} starts a new round and performs calculations based on the player's actions. | |
| 16 | * <br /> | |
| 17 | * In each round there is a chance for famine-induced loss, | |
| 18 | * which is handled in {@link #processFamine()} based on an underlying random probability factor. | |
| 19 | * <br /> | |
| 20 | * Apart from these automated things the player can: | |
| 21 | * <ul> | |
| 22 | * <li>{@link #buyLand(Long)} buy new land</li> | |
| 23 | * <li>{@link #sellLand(Long)} sell existing land</li> | |
| 24 | * <li>{@link #cultivate(Long)} cultivate land in order to achieve new yields</li> | |
| 25 | * <li>{@link #feedToPopulation(Long)} give food to your population</li> | |
| 26 | * </ul> | |
| 27 | * After user interactions the round is finished in {@link #finishRoundAfterActions()}. | |
| 28 | */ | |
| 29 | @Getter | |
| 30 | public class KaiserEngine { | |
| 31 | /** | |
| 32 | * e - External damage, e.g. loss due to rats. | |
| 33 | */ | |
| 34 | private BigDecimal externalDamage = BigDecimal.ZERO; // e | |
| 35 | private BigDecimal deathToll; // d | |
| 36 | private BigDecimal increase; // i in original- birthRate? | |
| 37 | private Integer zYear; // why z in original? | |
| 38 | private BigDecimal population = BigDecimal.ZERO; // h in original | |
| 39 | private BigDecimal area = BigDecimal.ZERO; | |
| 40 | private BigDecimal yield = BigDecimal.ZERO; | |
| 41 | private BigDecimal supplies = BigDecimal.ZERO; | |
| 42 | private BigDecimal humans = BigDecimal.ZERO; | |
| 43 | private BigDecimal deathTollSum; // d1 in original | |
| 44 | private BigDecimal percentDeathToll; // p1 in original | |
| 45 | private BigDecimal q = BigDecimal.ONE; // q - disaster/famineQuotient | |
| 46 | private BigDecimal cost = BigDecimal.ZERO; | |
| 47 | ||
| 48 | private static final Random RANDOM = new SecureRandom(); | |
| 49 | ||
| 50 | /** | |
| 51 | * Default constructor to start a game with the given default settings. | |
| 52 | */ | |
| 53 | public KaiserEngine() { | |
| 54 | this.population = BigDecimal.valueOf(95L); | |
| 55 | this.zYear = 0; | |
| 56 | this.yield = BigDecimal.valueOf(3L); | |
| 57 | this.supplies = BigDecimal.valueOf(2800L); | |
| 58 | this.humans = BigDecimal.valueOf(3000L); | |
| 59 | this.area = this.humans.divide(this.yield, RoundingMode.HALF_UP); | |
| 60 | this.increase = BigDecimal.valueOf(5L); | |
| 61 | this.deathToll = BigDecimal.ZERO; | |
| 62 | this.percentDeathToll = BigDecimal.ZERO; | |
| 63 | this.deathTollSum = BigDecimal.ZERO; | |
| 64 | this.externalDamage = this.humans.subtract(this.supplies); | |
| 65 | } | |
| 66 | ||
| 67 | /** | |
| 68 | * Starts a new round in performs initial calculations before user actions are taken into account. | |
| 69 | */ | |
| 70 | public void startNewRound() { | |
| 71 | this.area = this.humans.divide(this.yield, 0, RoundingMode.HALF_UP); | |
| 72 | this.externalDamage = this.humans.subtract(this.supplies); | |
| 73 |
1
1. startNewRound : Replaced integer addition with subtraction → KILLED |
this.zYear++; |
| 74 | this.population = this.population.add(this.increase); | |
| 75 | ||
| 76 |
1
1. startNewRound : removed call to de/aikiit/game/kaiser/KaiserEngine::processFamine → SURVIVED |
processFamine(); |
| 77 | this.cost = getRandomNumberUntil(10); | |
| 78 | this.yield = cost.add(BigDecimal.valueOf(17L)); | |
| 79 | } | |
| 80 | ||
| 81 | /** | |
| 82 | * Helper method to retrieve a new random number without any comma (scale=0). | |
| 83 | * | |
| 84 | * @param threshold number is greater than 0 and at most threshold. | |
| 85 | * @return a new random number. | |
| 86 | */ | |
| 87 | BigDecimal getRandomNumberUntil(int threshold) { | |
| 88 |
3
1. getRandomNumberUntil : Replaced integer addition with subtraction → SURVIVED 2. getRandomNumberUntil : Replaced integer addition with subtraction → KILLED 3. getRandomNumberUntil : replaced return value with null for de/aikiit/game/kaiser/KaiserEngine::getRandomNumberUntil → KILLED |
return BigDecimal.valueOf(RANDOM.nextInt(threshold + 1) + 1).setScale(0, RoundingMode.HALF_EVEN); |
| 89 | } | |
| 90 | ||
| 91 | /** | |
| 92 | * Evaluate internally, if a famine is happening in the current round. | |
| 93 | * If so this method performs all necessary calculations/reductions within the currently running game. | |
| 94 | */ | |
| 95 | public void processFamine() { | |
| 96 |
1
1. processFamine : negated conditional → KILLED |
if (is(q).lessThan(BigDecimal.ZERO)) { |
| 97 | this.population = this.population.divide(BigDecimal.valueOf(2L), 0, RoundingMode.HALF_UP); | |
| 98 |
1
1. processFamine : removed call to java/io/PrintStream::println → SURVIVED |
System.out.println(KaiserEnginePrinter.ORANGE); |
| 99 |
1
1. processFamine : removed call to java/io/PrintStream::println → SURVIVED |
System.out.println("Eine fürchterliche Seuche hat die halbe Stadt dahingerafft!"); |
| 100 |
1
1. processFamine : removed call to java/io/PrintStream::println → SURVIVED |
System.out.println(KaiserEnginePrinter.ANSI_RESET); |
| 101 | } | |
| 102 |
1
1. processFamine : removed call to de/aikiit/game/kaiser/KaiserEngine::refreshFamineQuotient → SURVIVED |
refreshFamineQuotient(); |
| 103 | } | |
| 104 | ||
| 105 | /** | |
| 106 | * Explicitly trigger the recalculation of the given internal famine calculation factor. | |
| 107 | */ | |
| 108 | void refreshFamineQuotient() { | |
| 109 | this.q = getRandomNumberUntil(10).divide(BigDecimal.TEN, 0, RoundingMode.HALF_UP).subtract(new BigDecimal("0.3")); | |
| 110 | ||
| 111 | } | |
| 112 | ||
| 113 | /** | |
| 114 | * Allow setting the area value for testing purposes. | |
| 115 | * @param q q value. | |
| 116 | */ | |
| 117 | @VisibleForTesting | |
| 118 | void setQ(BigDecimal q) { | |
| 119 | this.q = q; | |
| 120 | } | |
| 121 | ||
| 122 | /** | |
| 123 | * Allow setting the supplies value for testing purposes. | |
| 124 | * @param supplies current supplies. | |
| 125 | */ | |
| 126 | @VisibleForTesting | |
| 127 | void setSupplies(BigDecimal supplies) { | |
| 128 | this.supplies = supplies; | |
| 129 | } | |
| 130 | ||
| 131 | /** | |
| 132 | * Allow setting the area value for testing purposes. | |
| 133 | * @param area current area. | |
| 134 | */ | |
| 135 | @VisibleForTesting | |
| 136 | void setArea(BigDecimal area) { | |
| 137 | this.area = area; | |
| 138 | } | |
| 139 | ||
| 140 | /** | |
| 141 | * Calculates the available area per person in the current game. | |
| 142 | * | |
| 143 | * @return area per capita, called <b>L</b> in original. Land ownership? | |
| 144 | */ | |
| 145 | public BigDecimal getAreaPerCapita() { | |
| 146 |
1
1. getAreaPerCapita : replaced return value with null for de/aikiit/game/kaiser/KaiserEngine::getAreaPerCapita → KILLED |
return area.divide(population, 0, RoundingMode.HALF_UP); |
| 147 | } | |
| 148 | ||
| 149 | /** | |
| 150 | * Performs an act of buying land (new land is acquired by reducing the supplies according to the current land price). | |
| 151 | * You cannot buy more than you can afford. | |
| 152 | * | |
| 153 | * @param buy how many hectares you want to buy. Negative input is ignored. | |
| 154 | * @return the given number of hectares. {@code 0} means that the player does not want to buy anything, which will trigger the possibility to sell land. | |
| 155 | */ | |
| 156 | public long buyLand(Long buy) { | |
| 157 |
2
1. buyLand : negated conditional → SURVIVED 2. buyLand : changed conditional boundary → SURVIVED |
if (buy < 0) { |
| 158 |
1
1. buyLand : removed call to java/io/PrintStream::println → SURVIVED |
System.out.println(KaiserEnginePrinter.ANSI_PURPLE + "Ignoriere negative Eingaben - Du willst mich wohl verkackeiern." + KaiserEnginePrinter.ANSI_RESET); |
| 159 | } | |
| 160 | ||
| 161 |
2
1. buyLand : changed conditional boundary → SURVIVED 2. buyLand : negated conditional → KILLED |
if (buy > 0) { |
| 162 |
1
1. buyLand : negated conditional → KILLED |
if (is(this.yield.multiply(BigDecimal.valueOf(buy))).lessThanOrEqualTo(this.supplies)) { |
| 163 | this.area = this.area.add(BigDecimal.valueOf(buy)); | |
| 164 | this.supplies = this.supplies.subtract(this.yield.multiply(BigDecimal.valueOf(buy))); | |
| 165 | this.cost = BigDecimal.ZERO; // price is recalculated per round | |
| 166 | } else { | |
| 167 | throw new IllegalArgumentException("Not Enough Supplies"); | |
| 168 | } | |
| 169 | } | |
| 170 |
1
1. buyLand : replaced long return with 0 for de/aikiit/game/kaiser/KaiserEngine::buyLand → KILLED |
return buy; |
| 171 | } | |
| 172 | ||
| 173 | /** | |
| 174 | * Performs an act of selling land (resulting in an increase of supplies as the land is sold to the current land price). | |
| 175 | * You cannot sell more than you have. | |
| 176 | * | |
| 177 | * @param sell how many hectares you want to sell. Negative input is ignored. | |
| 178 | */ | |
| 179 | public void sellLand(Long sell) { | |
| 180 |
2
1. sellLand : changed conditional boundary → SURVIVED 2. sellLand : negated conditional → KILLED |
if (sell < 0) { |
| 181 |
1
1. sellLand : removed call to java/io/PrintStream::println → SURVIVED |
System.out.println(KaiserEnginePrinter.ANSI_PURPLE + "Ignoriere negative Eingaben - Du willst mich wohl verkackeiern." + KaiserEnginePrinter.ANSI_RESET); |
| 182 | return; | |
| 183 | } | |
| 184 | ||
| 185 |
1
1. sellLand : negated conditional → KILLED |
if (is(BigDecimal.valueOf(sell)).lessThan(this.area)) { |
| 186 | this.area = this.area.subtract(BigDecimal.valueOf(sell)); | |
| 187 | this.supplies = this.supplies.add(this.yield.multiply(BigDecimal.valueOf(sell))); | |
| 188 | this.cost = BigDecimal.ZERO; // price is recalculated per round | |
| 189 | } else { | |
| 190 | throw new IllegalArgumentException("Not Enough Land"); | |
| 191 | } | |
| 192 | } | |
| 193 | ||
| 194 | /** | |
| 195 | * Performs an act of using supplies to feed your population. | |
| 196 | * You cannot give more than you have. | |
| 197 | * | |
| 198 | * @param feed how many dzt you want to feed | |
| 199 | */ | |
| 200 | public void feedToPopulation(Long feed) { | |
| 201 |
2
1. feedToPopulation : changed conditional boundary → SURVIVED 2. feedToPopulation : negated conditional → KILLED |
if (feed < 0) { |
| 202 |
1
1. feedToPopulation : removed call to java/io/PrintStream::println → SURVIVED |
System.out.println(KaiserEnginePrinter.ANSI_PURPLE + "Ignoriere negative Eingaben - Du willst mich wohl verkackeiern." + KaiserEnginePrinter.ANSI_RESET); |
| 203 | return; | |
| 204 | } | |
| 205 | ||
| 206 |
1
1. feedToPopulation : negated conditional → KILLED |
if (feed != 0) { |
| 207 |
1
1. feedToPopulation : negated conditional → KILLED |
if (is(BigDecimal.valueOf(feed)).lessThanOrEqualTo(this.supplies)) { |
| 208 | this.supplies = this.supplies.subtract(BigDecimal.valueOf(feed)); | |
| 209 | this.cost = BigDecimal.ONE; // price is recalculated per round | |
| 210 | } else { | |
| 211 | throw new IllegalArgumentException("Not Enough in Stock"); | |
| 212 | } | |
| 213 | } | |
| 214 | } | |
| 215 | ||
| 216 | /** | |
| 217 | * Performs an act of using your area and people to cultivate, plant crops for the upcoming season/next round. | |
| 218 | * You cannot give more than you have. | |
| 219 | * | |
| 220 | * @param cultivate how many hectares you want to use for agricultural purposes. Negative input is ignored. An input of {@code 0} will trigger a recalculation of the current land price. | |
| 221 | */ | |
| 222 | public void cultivate(Long cultivate) { | |
| 223 |
1
1. cultivate : negated conditional → KILLED |
if (cultivate == 0) { |
| 224 |
1
1. cultivate : removed call to de/aikiit/game/kaiser/KaiserEngine::calculateNewPrice → KILLED |
calculateNewPrice(); |
| 225 | return; | |
| 226 | } | |
| 227 | ||
| 228 |
2
1. cultivate : changed conditional boundary → SURVIVED 2. cultivate : negated conditional → KILLED |
if (cultivate < 0) { |
| 229 |
1
1. cultivate : removed call to java/io/PrintStream::println → SURVIVED |
System.out.println(KaiserEnginePrinter.ANSI_PURPLE + "Ignoriere negative Eingaben - Du willst mich wohl verkackeiern." + KaiserEnginePrinter.ANSI_RESET); |
| 230 | return; | |
| 231 | } | |
| 232 | ||
| 233 |
1
1. cultivate : negated conditional → KILLED |
if (is(this.area).lessThan(BigDecimal.valueOf(cultivate))) { |
| 234 | throw new IllegalArgumentException("You cannot cultivate more area than you have."); | |
| 235 | } | |
| 236 | ||
| 237 | BigDecimal halfCultivate = BigDecimal.valueOf(cultivate).divide(BigDecimal.valueOf(2L), 0, RoundingMode.HALF_UP); | |
| 238 |
1
1. cultivate : negated conditional → KILLED |
if (is(this.supplies).lessThan(halfCultivate)) { |
| 239 | throw new IllegalArgumentException("You cannot cultivate more than you have."); | |
| 240 | } | |
| 241 | ||
| 242 |
1
1. cultivate : negated conditional → KILLED |
if (is(BigDecimal.valueOf(cultivate)).greaterThan(getPopulation().multiply(BigDecimal.TEN))) { |
| 243 | throw new IllegalArgumentException("Not enough workers available."); | |
| 244 | } | |
| 245 | ||
| 246 | // perform seeding | |
| 247 | this.supplies = this.supplies.subtract(halfCultivate); | |
| 248 |
1
1. cultivate : removed call to de/aikiit/game/kaiser/KaiserEngine::calculateNewPrice → SURVIVED |
calculateNewPrice(); |
| 249 | ||
| 250 | // yields after cultivation and population increase | |
| 251 | this.yield = this.cost; | |
| 252 | this.humans = this.yield.multiply(BigDecimal.valueOf(cultivate)); | |
| 253 | ||
| 254 | // cultivation kills rats ;) | |
| 255 | this.externalDamage = BigDecimal.ZERO; | |
| 256 |
1
1. cultivate : removed call to de/aikiit/game/kaiser/KaiserEngine::calculateNewPrice → SURVIVED |
calculateNewPrice(); |
| 257 | ||
| 258 | // but add some external damage in some cases in a naiive manner | |
| 259 | // original condition stated: if int(c/2) <> c/2 | |
| 260 |
1
1. cultivate : negated conditional → SURVIVED |
if (this.cost.divide(BigDecimal.valueOf(2L), 0, RoundingMode.DOWN).intValue() == this.cost.divide(BigDecimal.valueOf(2L), 0, RoundingMode.UP).intValue()) { |
| 261 | this.externalDamage = this.supplies.divide(this.cost, 0, RoundingMode.HALF_UP); | |
| 262 | } | |
| 263 | this.supplies = this.supplies.subtract(this.externalDamage).add(this.humans); | |
| 264 |
1
1. cultivate : removed call to de/aikiit/game/kaiser/KaiserEngine::calculateNewPrice → SURVIVED |
calculateNewPrice(); |
| 265 | } | |
| 266 | ||
| 267 | /** | |
| 268 | * Change price for next round. | |
| 269 | */ | |
| 270 | @VisibleForTesting | |
| 271 | void calculateNewPrice() { | |
| 272 | this.cost = getRandomNumberUntil(5); | |
| 273 | } | |
| 274 | ||
| 275 | /** | |
| 276 | * Perform "round"-final calculations such as | |
| 277 | * <ul> | |
| 278 | * <li>number of people that died</li> | |
| 279 | * <li>adapt overall (internal) death statistics</li> | |
| 280 | * <li>refresh internal famine quotient</li> | |
| 281 | * </ul> | |
| 282 | */ | |
| 283 | public void finishRoundAfterActions() { | |
| 284 | BigDecimal factor = BigDecimal.valueOf(20L).multiply(this.area).add(this.supplies); | |
| 285 | this.increase = cost.multiply(factor).divide(this.population, 0, RoundingMode.HALF_UP).divide(BigDecimal.valueOf(100).add(BigDecimal.ONE), 0, RoundingMode.HALF_UP); | |
| 286 | ||
| 287 | this.cost = this.q.divide(BigDecimal.valueOf(20L), 0, RoundingMode.HALF_UP); | |
| 288 |
1
1. finishRoundAfterActions : removed call to de/aikiit/game/kaiser/KaiserEngine::refreshFamineQuotient → SURVIVED |
refreshFamineQuotient(); |
| 289 | ||
| 290 |
1
1. finishRoundAfterActions : negated conditional → SURVIVED |
if (is(this.population).lessThan(this.cost)) { |
| 291 | this.deathToll = BigDecimal.ZERO; | |
| 292 | return; // start new round without any deaths | |
| 293 | } | |
| 294 | ||
| 295 | // calculate deaths | |
| 296 | this.deathToll = this.population.subtract(this.cost); | |
| 297 |
1
1. finishRoundAfterActions : negated conditional → KILLED |
if (is(this.deathToll).greaterThan(this.population.multiply(BigDecimal.valueOf(0.45)))) { |
| 298 |
1
1. finishRoundAfterActions : removed call to java/io/PrintStream::println → SURVIVED |
System.out.println(KaiserEnginePrinter.ANSI_YELLOW); |
| 299 |
1
1. finishRoundAfterActions : removed call to java/io/PrintStream::println → SURVIVED |
System.out.println("Sie haben " + this.deathToll + " Menschen in nur einem Jahr verhungern lassen!"); |
| 300 |
1
1. finishRoundAfterActions : removed call to java/io/PrintStream::println → SURVIVED |
System.out.println("Auf Grund dieser extremen Misswirtschaft, werden Sie nicht nur aus Amt und Würden gejagt,"); |
| 301 |
1
1. finishRoundAfterActions : removed call to java/io/PrintStream::println → SURVIVED |
System.out.println("sondern auch zum Versager des Jahres erklärt."); |
| 302 |
1
1. finishRoundAfterActions : removed call to java/io/PrintStream::println → SURVIVED |
System.out.println(KaiserEnginePrinter.ANSI_RESET); |
| 303 | return; // TODO stop the game here! | |
| 304 | } | |
| 305 | ||
| 306 | // calc death statistics | |
| 307 | // p1 = ((z-1)*p1+D*100/p)/z | |
| 308 |
1
1. finishRoundAfterActions : Replaced integer subtraction with addition → NO_COVERAGE |
BigDecimal tempQuotient = this.percentDeathToll.multiply(BigDecimal.valueOf(this.zYear - 1)).add(this.deathToll.multiply(BigDecimal.valueOf(100)).divide(this.population, 0, RoundingMode.HALF_UP)); |
| 309 | this.percentDeathToll = tempQuotient.divide(BigDecimal.valueOf(this.zYear), 0, RoundingMode.HALF_UP); | |
| 310 | ||
| 311 | this.population = this.cost; // TODO why? shouldn't this somehow be added up? | |
| 312 | this.deathTollSum = this.deathTollSum.add(this.deathToll); | |
| 313 | } | |
| 314 | } | |
Mutations | ||
| 73 |
1.1 |
|
| 76 |
1.1 |
|
| 88 |
1.1 2.2 3.3 |
|
| 96 |
1.1 |
|
| 98 |
1.1 |
|
| 99 |
1.1 |
|
| 100 |
1.1 |
|
| 102 |
1.1 |
|
| 146 |
1.1 |
|
| 157 |
1.1 2.2 |
|
| 158 |
1.1 |
|
| 161 |
1.1 2.2 |
|
| 162 |
1.1 |
|
| 170 |
1.1 |
|
| 180 |
1.1 2.2 |
|
| 181 |
1.1 |
|
| 185 |
1.1 |
|
| 201 |
1.1 2.2 |
|
| 202 |
1.1 |
|
| 206 |
1.1 |
|
| 207 |
1.1 |
|
| 223 |
1.1 |
|
| 224 |
1.1 |
|
| 228 |
1.1 2.2 |
|
| 229 |
1.1 |
|
| 233 |
1.1 |
|
| 238 |
1.1 |
|
| 242 |
1.1 |
|
| 248 |
1.1 |
|
| 256 |
1.1 |
|
| 260 |
1.1 |
|
| 264 |
1.1 |
|
| 288 |
1.1 |
|
| 290 |
1.1 |
|
| 297 |
1.1 |
|
| 298 |
1.1 |
|
| 299 |
1.1 |
|
| 300 |
1.1 |
|
| 301 |
1.1 |
|
| 302 |
1.1 |
|
| 308 |
1.1 |