diff --git a/53_King/README.md b/53_King/README.md index eb0ba8ab..206202f0 100644 --- a/53_King/README.md +++ b/53_King/README.md @@ -8,6 +8,28 @@ The money system is Rollods; each person needs 100 Rallods per year to survive. The author of this program is James A. Storer who wrote it while a student at Lexington High School. +## Bugs + +Implementers should be aware that this game contains at least one bug. + +On basic line 1450 + + 1450 V3=INT(A+V3) + 1451 A=INT(A+V3) + +...where A is the current treasury, and V3 is initially zero. +This would mean that the treasury doubles at the end of the first year, and all calculations for an increase in the treasury due to tourism are discarded. +Possibly, this made the game more playable, although impossible for the player to understand why the treasury was increasing? + +A quick fix for this bug in the original code would be + + 1450 V3=ABS(INT(V1-V2)) + 1451 A=INT(A+V3) + +...judging from the description of tourist income on basic line 1410 + + 1410 PRINT " YOU MADE";ABS(INT(V1-V2));"RALLODS FROM TOURIST TRADE." + --- As published in Basic Computer Games (1978): diff --git a/53_King/king_variable_update.bas b/53_King/king_variable_update.bas new file mode 100644 index 00000000..c88edf93 --- /dev/null +++ b/53_King/king_variable_update.bas @@ -0,0 +1,306 @@ +1 PRINT TAB(34);"KING" +2 PRINT TAB(15);"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY" +3 PRINT:PRINT:PRINT +4 PRINT "DO YOU WANT INSTRUCTIONS"; +5 INPUT Z$ +6 YEARS_REQUIRED=8 +10 IF LEFT$(Z$,1)="N" THEN 47 + 11 IF Z$="AGAIN" THEN 1960 + 12 PRINT:PRINT:PRINT + 20 PRINT "CONGRATULATIONS! YOU'VE JUST BEEN ELECTED PREMIER OF SETATS" + 22 PRINT "DETINU, RALLODS SMALL COMMUNIST ISLAND 30 BY 70 MILES LONG. YOUR" + 24 PRINT "JOB IS TO DECIDE UPON THE CONTRY'S BUDGET AND DISTRIBUTE" + 26 PRINT "MONEY TO YOUR COUNTRYMEN FROM THE COMMUNAL TREASURY." + 28 PRINT "THE MONEY SYSTEM IS RALLODS, AND EACH PERSON NEEDS 100" + 30 PRINT "RALLODS PER YEAR TO SURVIVE. YOUR COUNTRY'S INCOME COMES" + 32 PRINT "FROM FARM PRODUCE AND TOURISTS VISITING YOUR MAGNIFICENT" + 34 PRINT "FORESTS, HUNTING, FISHING, ETC. HALF YOUR LAND IS FARM LAND" + 36 PRINT "WHICH ALSO HAS AN EXCELLENT MINERAL CONTENT AND MAY BE SOLD" + 38 PRINT "TO FOREIGN INDUSTRY (STRIP MINING) WHO IMPORT AND SUPPORT" + 40 PRINT "THEIR OWN WORKERS. CROPS COST BETWEEN 10 AND 15 RALLODS PER" + 42 PRINT "SQUARE MILE TO PLANT." + 44 PRINT "YOUR GOAL IS TO COMPLETE YOUR";YEARS_REQUIRED;"YEAR TERM OF OFFICE." + 46 PRINT "GOOD LUCK!" + +47 PRINT + +50 RALLODS=INT(60000+(1000*RND(1))-(1000*RND(1))) +55 COUNTRYMEN=INT(500+(10*RND(1))-(10*RND(1))) +65 LANDAREA=2000 +100 LANDPRICE=INT(10*RND(1)+95) +102 PRINT +105 PRINT "YOU NOW HAVE ";RALLODS;" RALLODS IN THE TREASURY." +110 PRINT INT(COUNTRYMEN);:PRINT "COUNTRYMEN, "; +115 COST_TO_PLANT=INT(((RND(1)/2)*10+10)) +120 IF FOREIGN_WORKERS=0 THEN 140 +130 PRINT INT(FOREIGN_WORKERS);"FOREIGN WORKERS, "; +140 PRINT "AND";INT(LANDAREA);"SQ. MILES OF LAND." +150 PRINT "THIS YEAR INDUSTRY WILL BUY LAND FOR";LANDPRICE; +152 PRINT "RALLODS PER SQUARE MILE." +155 PRINT "LAND CURRENTLY COSTS";COST_TO_PLANT;"RALLODS PER SQUARE MILE TO PLANT." +162 PRINT + +200 PRINT "HOW MANY SQUARE MILES DO YOU WISH TO SELL TO INDUSTRY"; +210 INPUT SELL_TO_INDUSTRY +215 IF SELL_TO_INDUSTRY<0 THEN 200 +220 IF SELL_TO_INDUSTRY<=LANDAREA-1000 THEN 300 +230 PRINT "*** THINK AGAIN. YOU ONLY HAVE";LANDAREA-1000;"SQUARE MILES OF FARM LAND." + +240 IF EXPLANATION_GIVEN THEN 200 + 250 PRINT:PRINT "(FOREIGN INDUSTRY WILL ONLY BUY FARM LAND BECAUSE" + 260 PRINT "FOREST LAND IS UNECONOMICAL TO STRIP MINE DUE TO TREES," + 270 PRINT "THICKER TOP SOIL, ETC.)" + 280 EXPLANATION_GIVEN=TRUE +299 GOTO 200 + +300 LANDAREA=INT(LANDAREA-SELL_TO_INDUSTRY) +310 RALLODS=INT(RALLODS+(SELL_TO_INDUSTRY*LANDPRICE)) +320 PRINT "HOW MANY RALLODS WILL YOU DISTRIBUTE AMONG YOUR COUNTRYMEN"; +340 INPUT WELFARE +342 IF WELFARE<0 THEN 320 +350 IF WELFARE0 THEN 1002 +602 IF WELFARE<>0 THEN 1002 +604 IF PLANTING_AREA<>0 THEN 1002 +606 IF MONEY_SPENT_ON_POLLUTION_CONTROL<>0 THEN 1002 + +609 PRINT +612 PRINT "GOODBYE." +614 PRINT "(IF YOU WISH TO CONTINUE THIS GAME AT A LATER DATE, ANSWER" +616 PRINT "'AGAIN' WHEN ASKED IF YOU WANT INSTRUCTIONS AT THE START" +617 PRINT "OF THE GAME)." +618 STOP + +1000 GOTO 600 + +1002 PRINT +1003 PRINT + +1010 RALLODS=INT(RALLODS-MONEY_SPENT_ON_POLLUTION_CONTROL) +1020 ORIGINAL_RALLODS=RALLODS + +1100 IF INT(WELFARE/100-COUNTRYMEN)>=0 THEN 1120 +1105 IF WELFARE/100<50 THEN 1700 +1110 PRINT INT(COUNTRYMEN-(WELFARE/100));"COUNTRYMEN DIED OF STARVATION" + +1120 POLLUTION_DEATHS=INT(RND(1)*(2000-LANDAREA)) +1122 IF MONEY_SPENT_ON_POLLUTION_CONTROL<25 THEN 1130 +1125 POLLUTION_DEATHS=INT(POLLUTION_DEATHS/(MONEY_SPENT_ON_POLLUTION_CONTROL/25)) + +1130 IF POLLUTION_DEATHS<=0 THEN 1150 +1140 PRINT POLLUTION_DEATHS;"COUNTRYMEN DIED OF CARBON-MONOXIDE AND DUST INHALATION" + +1150 IF INT((WELFARE/100)-COUNTRYMEN)<0 THEN 1170 +1160 IF POLLUTION_DEATHS>0 THEN 1180 +1165 GOTO 1200 + +1170 PRINT " YOU WERE FORCED TO SPEND";INT((POLLUTION_DEATHS+(COUNTRYMEN-(WELFARE/100)))*9); +1172 PRINT "RALLODS ON FUNERAL EXPENSES" +1174 DEATHS=INT(POLLUTION_DEATHS+(COUNTRYMEN-(WELFARE/100))) +1175 RALLODS=INT(RALLODS-((POLLUTION_DEATHS+(COUNTRYMEN-(WELFARE/100)))*9)) +1176 GOTO 1185 + +1180 PRINT " YOU WERE FORCED TO SPEND ";INT(POLLUTION_DEATHS*9);"RALLODS ON "; +1181 PRINT "FUNERAL EXPENSES." +1182 DEATHS=POLLUTION_DEATHS +1183 RALLODS=INT(RALLODS-(POLLUTION_DEATHS*9)) + +1185 IF RALLODS>=0 THEN 1194 +1187 PRINT " INSUFFICIENT RESERVES TO COVER COST - LAND WAS SOLD" +1189 LANDAREA=INT(LANDAREA+(RALLODS/LANDPRICE)) +1190 RALLODS=0 + +1194 COUNTRYMEN=INT(COUNTRYMEN-DEATHS) + +1200 IF SELL_TO_INDUSTRY=0 THEN 1250 +1220 NEW_FOREIGNERS=INT(SELL_TO_INDUSTRY+(RND(1)*10)-(RND(1)*20)) +1224 IF FOREIGN_WORKERS>0 THEN 1230 +1226 NEW_FOREIGNERS=NEW_FOREIGNERS+20 + +1230 PRINT NEW_FOREIGNERS;"WORKERS CAME TO THE COUNTRY AND"; + +1250 IMMIGRATION=INT(((WELFARE/100-COUNTRYMEN)/10)+(MONEY_SPENT_ON_POLLUTION_CONTROL/25)-((2000-LANDAREA)/50)-(POLLUTION_DEATHS/2)) +1255 PRINT ABS(IMMIGRATION);"COUNTRYMEN "; +1260 IF IMMIGRATION<0 THEN 1275 +1265 PRINT "CAME TO"; +1270 GOTO 1280 +1275 PRINT "LEFT"; +1280 PRINT " THE ISLAND." +1290 COUNTRYMEN=INT(COUNTRYMEN+IMMIGRATION) + + +1292 FOREIGN_WORKERS=INT(FOREIGN_WORKERS+NEW_FOREIGNERS) + +1305 CROP_LOSS=INT(((2000-LANDAREA)*((RND(1)+1.5)/2))) +1310 IF FOREIGN_WORKERS=0 THEN 1324 +1320 PRINT "OF ";INT(PLANTING_AREA);"SQ. MILES PLANTED,"; +1324 IF PLANTING_AREA>CROP_LOSS THEN 1330 +1326 CROP_LOSS=PLANTING_AREA +1330 PRINT " YOU HARVESTED ";INT(PLANTING_AREA-CROP_LOSS);"SQ. MILES OF CROPS." +1340 IF CROP_LOSS=0 THEN 1370 +1344 IF T1>=2 THEN 1370 +1350 PRINT " (DUE TO "; +1355 IF T1=0 THEN 1365 +1360 PRINT "INCREASED "; +1365 PRINT "AIR AND WATER POLLUTION FROM FOREIGN INDUSTRY.)" +1370 AGRICULTURAL_INCOME=INT((PLANTING_AREA-CROP_LOSS)*(LANDPRICE/2)) +1380 PRINT "MAKING";INT(AGRICULTURAL_INCOME);"RALLODS." +1390 RALLODS=INT(RALLODS+AGRICULTURAL_INCOME) + +REM I think tourism calculations are actually wrong in the original code! + +1400 V1=INT(((COUNTRYMEN-IMMIGRATION)*22)+(RND(1)*500)) +1405 V2=INT((2000-LANDAREA)*15) +1410 PRINT " YOU MADE";ABS(INT(V1-V2));"RALLODS FROM TOURIST TRADE." +1420 IF V2=0 THEN 1450 +1425 IF V1-V2>=V3 THEN 1450 +1430 PRINT " DECREASE BECAUSE "; +1435 G1=10*RND(1) +1440 IF G1<=2 THEN 1460 +1442 IF G1<=4 THEN 1465 +1444 IF G1<=6 THEN 1470 +1446 IF G1<=8 THEN 1475 +1448 IF G1<=10 THEN 1480 +1450 V3=INT(RALLODS+V3) +1451 RALLODS=INT(RALLODS+V3) +1452 GOTO 1500 +1460 PRINT "FISH POPULATION HAS DWINDLED DUE TO WATER POLLUTION." +1462 GOTO 1450 +1465 PRINT "AIR POLLUTION IS KILLING GAME BIRD POPULATION." +1467 GOTO 1450 +1470 PRINT "MINERAL BATHS ARE BEING RUINED BY WATER POLLUTION." +1472 GOTO 1450 +1475 PRINT "UNPLEASANT SMOG IS DISCOURAGING SUN BATHERS." +1477 GOTO 1450 +1480 PRINT "HOTELS ARE LOOKING SHABBY DUE TO SMOG GRIT." +1482 GOTO 1450 +1500 IF DEATHS>200 THEN 1600 +1505 IF COUNTRYMEN<343 THEN 1700 +1510 IF (ORIGINAL_RALLODS/100)>5 THEN 1800 +1515 IF FOREIGN_WORKERS>COUNTRYMEN THEN 1550 +1520 IF YEARS_REQUIRED-1=X5 THEN 1900 +1545 GOTO 2000 +1550 PRINT +1552 PRINT +1560 PRINT "THE NUMBER OF FOREIGN WORKERS HAS EXCEEDED THE NUMBER" +1562 PRINT "OF COUNTRYMEN. AS A MINORITY, THEY HAVE REVOLTED AND" +1564 PRINT "TAKEN OVER THE COUNTRY." +1570 IF RND(1)<=.5 THEN 1580 +1574 PRINT "YOU HAVE BEEN THROWN OUT OF OFFICE AND ARE NOW" +1576 PRINT "RESIDING IN PRISON." +1578 GOTO 1590 +1580 PRINT "YOU HAVE BEEN ASSASSINATED." +1590 PRINT +1592 PRINT +1596 STOP +1600 PRINT +1602 PRINT +1610 PRINT DEATHS;"COUNTRYMEN DIED IN ONE YEAR!!!!!" +1615 PRINT "DUE TO THIS EXTREME MISMANAGEMENT, YOU HAVE NOT ONLY" +1620 PRINT "BEEN IMPEACHED AND THROWN OUT OF OFFICE, BUT YOU" +1622 M6=INT(RND(1)*10) +1625 IF M6<=3 THEN 1670 +1630 IF M6<=6 THEN 1680 +1635 IF M6<=10 THEN 1690 +1670 PRINT "ALSO HAD YOUR LEFT EYE GOUGED OUT!" +1672 GOTO 1590 +1680 PRINT "HAVE ALSO GAINED A VERY BAD REPUTATION." +1682 GOTO 1590 +1690 PRINT "HAVE ALSO BEEN DECLARED NATIONAL FINK." +1692 GOTO 1590 + +1700 PRINT +1702 PRINT +1710 PRINT "OVER ONE THIRD OF THE POPULTATION HAS DIED SINCE YOU" +1715 PRINT "WERE ELECTED TO OFFICE. THE PEOPLE (REMAINING)" +1720 PRINT "HATE YOUR GUTS." +1730 GOTO 1570 +1800 IF DEATHS-POLLUTION_DEATHS<2 THEN 1515 +1807 PRINT +1815 PRINT "MONEY WAS LEFT OVER IN THE TREASURY WHICH YOU DID" +1820 PRINT "NOT SPEND. AS A RESULT, SOME OF YOUR COUNTRYMEN DIED" +1825 PRINT "OF STARVATION. THE PUBLIC IS ENRAGED AND YOU HAVE" +1830 PRINT "BEEN FORCED TO EITHER RESIGN OR COMMIT SUICIDE." +1835 PRINT "THE CHOICE IS YOURS." +1840 PRINT "IF YOU CHOOSE THE LATTER, PLEASE TURN OFF YOUR COMPUTER" +1845 PRINT "BEFORE PROCEEDING." +1850 GOTO 1590 +1900 PRINT +1902 PRINT +1920 PRINT "CONGRATULATIONS!!!!!!!!!!!!!!!!!!" +1925 PRINT "YOU HAVE SUCCESFULLY COMPLETED YOUR";YEARS_REQUIRED;"YEAR TERM" +1930 PRINT "OF OFFICE. YOU WERE, OF COURSE, EXTREMELY LUCKY, BUT" +1935 PRINT "NEVERTHELESS, IT'S QUITE AN ACHIEVEMENT. GOODBYE AND GOOD" +1940 PRINT "LUCK - YOU'LL PROBABLY NEED IT IF YOU'RE THE TYPE THAT" +1945 PRINT "PLAYS THIS GAME." +1950 GOTO 1590 + +1960 PRINT "HOW MANY YEARS HAD YOU BEEN IN OFFICE WHEN INTERRUPTED"; +1961 INPUT X5 +1962 IF X5<0 THEN 1590 +1963 IF X5<8 THEN 1969 +1965 PRINT " COME ON, YOUR TERM IN OFFICE IS ONLY";YEARS_REQUIRED;"YEARS." +1967 GOTO 1960 +1969 PRINT "HOW MUCH DID YOU HAVE IN THE TREASURY"; +1970 INPUT RALLODS +1971 IF RALLODS<0 THEN 1590 +1975 PRINT "HOW MANY COUNTRYMEN"; +1976 INPUT COUNTRYMEN +1977 IF COUNTRYMEN<0 THEN 1590 +1980 PRINT "HOW MANY WORKERS"; +1981 INPUT FOREIGN_WORKERS +1982 IF FOREIGN_WORKERS<0 THEN 1590 +1990 PRINT "HOW MANY SQUARE MILES OF LAND"; +1991 INPUT LANDAREA +1992 IF LANDAREA<0 THEN 1590 +1993 IF LANDAREA>2000 THEN 1996 +1994 IF LANDAREA>1000 THEN 100 +1996 PRINT " COME ON, YOU STARTED WITH 1000 SQ. MILES OF FARM LAND" +1997 PRINT " AND 10,000 SQ. MILES OF FOREST LAND." +1998 GOTO 1990 + +2000 X5=X5+1 +2020 DEATHS=0 +2040 GOTO 100 +2046 END diff --git a/53_King/kotlin/King.kt b/53_King/kotlin/King.kt new file mode 100644 index 00000000..01558c99 --- /dev/null +++ b/53_King/kotlin/King.kt @@ -0,0 +1,600 @@ +import kotlin.math.abs +import kotlin.random.Random +import kotlin.system.exitProcess + +lateinit var gameState: GameState +const val INCLUDE_BUGS_FROM_ORIGINAL = false + +val rnd: Double get() = Random.nextDouble() +fun tab(i: Int) = " ".repeat(i) +class EndOfInputException : Throwable() + +fun main() { + header() + + print("DO YOU WANT INSTRUCTIONS? ") + readLine()?.apply { + gameState = if (startsWith("AGAIN")) loadOldGame() else GameState() + if (startsWith("Y")) instructions(gameState.yearsRequired) + } + ?: throw EndOfInputException() + + try { + with(gameState) { + while(currentYear < yearsRequired) { + recalculateLandCost() + displayStatus() + inputLandSale() + performLandSale() + inputWelfare() + performWelfare() + inputPlantingArea() + performPlanting() + inputPollutionControl() + if (zeroInput()) { + displayExitMessage() + exitProcess(0) + } + simulateOneYear() + currentYear ++ + } + } + win(gameState.yearsRequired) + } catch (e: GameEndingException) { + e.displayConsequences() + } +} + +private fun header() { + println("${tab(34)}KING") + println("${tab(14)}CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + println() + println() + println() +} + +fun instructions(yearsRequired: Int) { + println(""" + + + CONGRATULATIONS! YOU'VE JUST BEEN ELECTED PREMIER OF SETATS + DETINU, A SMALL COMMUNIST ISLAND 30 BY 70 MILES LONG. YOUR + JOB IS TO DECIDE UPON THE CONTRY'S BUDGET AND DISTRIBUTE + MONEY TO YOUR COUNTRYMEN FROM THE COMMUNAL TREASURY. + THE MONEY SYSTEM IS RALLODS, AND EACH PERSON NEEDS 100 + RALLODS PER YEAR TO SURVIVE. YOUR COUNTRY'S INCOME COMES + FROM FARM PRODUCE AND TOURISTS VISITING YOUR MAGNIFICENT + FORESTS, HUNTING, FISHING, ETC. HALF YOUR LAND IS FARM LAND + WHICH ALSO HAS AN EXCELLENT MINERAL CONTENT AND MAY BE SOLD + TO FOREIGN INDUSTRY (STRIP MINING) WHO IMPORT AND SUPPORT + THEIR OWN WORKERS. CROPS COST BETWEEN 10 AND 15 RALLODS PER + SQUARE MILE TO PLANT. + YOUR GOAL IS TO COMPLETE YOUR $yearsRequired YEAR TERM OF OFFICE. + GOOD LUCK! + """.trimIndent() + ) +} + +fun loadOldGame(): GameState = GameState().apply { + + do { + var retry = false + print("HOW MANY YEARS HAD YOU BEEN IN OFFICE WHEN INTERRUPTED? ") + currentYear = numberInput() + + if (currentYear <= 0) + throw GameEndingException.DataEntryValidation() + + if (currentYear >= yearsRequired) { + println(" COME ON, YOUR TERM IN OFFICE IS ONLY $yearsRequired YEARS.") + retry = true + } + } while (retry) + + print("HOW MUCH DID YOU HAVE IN THE TREASURY? ") + rallods = numberInput() + if (rallods < 0) + throw GameEndingException.DataEntryValidation() + + print("HOW MANY WORKERS? ") + foreignWorkers = numberInput() + if (foreignWorkers < 0) + throw GameEndingException.DataEntryValidation() + + do { + var retry = false + print("HOW MANY SQUARE MILES OF LAND? ") + landArea = numberInput() + if (landArea<0) + throw GameEndingException.DataEntryValidation() + if (landArea > 2000 || landArea <= 1000) { + println(" COME ON, YOU STARTED WITH 1000 SQ. MILES OF FARM LAND") + println(" AND 10,000 SQ. MILES OF FOREST LAND.") + retry = true + } + } while (retry) + +} + + +/** + * All exceptions which indicate the premature ending of the game, due + * to mismanagement, starvation, revolution, or mis-entry of a game state. + */ +sealed class GameEndingException : Throwable() { + abstract fun displayConsequences() + + fun finalFate() { + if (rnd < .5) { + println("YOU HAVE BEEN THROWN OUT OF OFFICE AND ARE NOW") + println("RESIDING IN PRISON.") + } else { + println("YOU HAVE BEEN ASSASSINATED.") + } + println() + println() + } + + class ExtremeMismanagement(private val death: Int) : GameEndingException() { + override fun displayConsequences() { + println() + println("$death COUNTRYMEN DIED IN ONE YEAR!!!!!") + println("DUE TO THIS EXTREME MISMANAGEMENT, YOU HAVE NOT ONLY") + println("BEEN IMPEACHED AND THROWN OUT OF OFFICE, BUT YOU") + println( + when ((rnd * 10.0).toInt()) { + in 0..3 -> "ALSO HAD YOUR LEFT EYE GOUGED OUT!" + in 4..6 -> "HAVE ALSO GAINED A VERY BAD REPUTATION." + else -> "HAVE ALSO BEEN DECLARED NATIONAL FINK." + } + ) + } + } + + class TooManyPeopleDead : GameEndingException() { + // The mistyping of "population" is in the original game. + override fun displayConsequences() { + println(""" + + + OVER ONE THIRD OF THE POPULTATION HAS DIED SINCE YOU + WERE ELECTED TO OFFICE. THE PEOPLE (REMAINING) + HATE YOUR GUTS. + """.trimIndent()) + finalFate() + } + } + + class AntiImmigrationRevolution : GameEndingException() { + override fun displayConsequences() { + println(""" + THE NUMBER OF FOREIGN WORKERS HAS EXCEEDED THE NUMBER + OF COUNTRYMEN. AS A MINORITY, THEY HAVE REVOLTED AND + TAKEN OVER THE COUNTRY. + """.trimIndent()) + finalFate() + } + } + + class StarvationWithFullTreasury : GameEndingException() { + override fun displayConsequences() { + println(""" + MONEY WAS LEFT OVER IN THE TREASURY WHICH YOU DID + NOT SPEND. AS A RESULT, SOME OF YOUR COUNTRYMEN DIED + OF STARVATION. THE PUBLIC IS ENRAGED AND YOU HAVE + BEEN FORCED TO EITHER RESIGN OR COMMIT SUICIDE. + THE CHOICE IS YOURS. + IF YOU CHOOSE THE LATTER, PLEASE TURN OFF YOUR COMPUTER + BEFORE PROCEEDING. + """.trimIndent()) + } + } + + class DataEntryValidation : GameEndingException() { + override fun displayConsequences() { + // no action + } + } + + +} + +fun win(yearsRequired: Int) { + // The misspelling of "successfully" is in the original code. + println(""" + + CONGRATULATIONS!!!!!!!!!!!!!!!!!! + YOU HAVE SUCCESFULLY COMPLETED YOUR $yearsRequired YEAR TERM + OF OFFICE. YOU WERE, OF COURSE, EXTREMELY LUCKY, BUT + NEVERTHELESS, IT'S QUITE AN ACHIEVEMENT. GOODBYE AND GOOD + LUCK - YOU'LL PROBABLY NEED IT IF YOU'RE THE TYPE THAT + PLAYS THIS GAME. + + + """.trimIndent()) +} + +/** + * Record data, allow data input, and process the simulation for the game. + */ +class GameState(val yearsRequired: Int = 8) { + + /** + * The current year. Years start with zero, but we never + * output the current year. + */ + var currentYear = 0 + + /** + * Number of countrymen who have died of either pollution + * or starvation this year. + * It costs 9 rallods to bury a body. + * If you lose 200 people in one year, you will throw an {@see ExtremeMismanagementException} + */ + private var death = 0 + + /** + * Last year's tourist numbers. Use this to check whether the number + * of tourists has gone up or down each year. + */ + private var tourists = 0 + + private var moneySpentOnPollutionControl = 0 + private var moneySpentOnPlanting = 0 + + /** + * Current stock of rallods. + * Player starts with between 59000 and 61000 rallods, but + * mostly distributed close to 60000. 75% of the time it's + * between 59500 and 60500. + */ + var rallods = (60000.0 + (1000.0 * rnd) - (1000.0 * rnd)).toInt() + + /** + * Population. + * Initial population is about to 500. + * 75% of the time it's between 495 and 505. + */ + private var countrymen = (500 + (10 * rnd) - (10 * rnd)).toInt() + + /** + * Land sale price is evenly between 95 and 104 rallods per + * square mile. + * Price doesn't change over the course of the game. + */ + private var landPrice = (10 * rnd + 95).toInt() + + private var plantingArea = 0 + private var welfareThisYear = 0 + + /** + * Land area in square miles. Arable land is 1000 square miles less. + * Almost all calculations use landArea-1000 because only arable + * land is of any use. + */ + var landArea = 2000 + + /** + * Number of foreigners brought in by companies to whom you + * have sold land. If this gets higher than your population, there will + * be a revolution. + */ + var foreignWorkers = 0 + + /** + * Planting cost is recalculated every year. + */ + private var costToPlant: Int = 1 + + /** + * There is a brief explanation of land selling only + * on the first turn. + */ + private var explanationOfSellingGiven = false + + private var sellThisYear: Int = 0 + + /** + * Planting cost is recalculated every year + * at between 10 and 14 rallods. + */ + fun recalculateLandCost() { + costToPlant = ((rnd / 2.0) * 10.0 + 10.0).toInt() + } + + /** + * Show the current status of the world. + */ + fun displayStatus() { + println() + println("YOU NOW HAVE $rallods RALLODS IN THE TREASURY.") + print("$countrymen COUNTRYMEN, ") + if (foreignWorkers != 0) { + println("$foreignWorkers FOREIGN WORKERS, ") + } + println("AND $landArea SQ. MILES OF LAND.") + println("THIS YEAR INDUSTRY WILL BUY LAND FOR $landPrice") + println("RALLODS PER SQUARE MILE.") + println("LAND CURRENTLY COSTS $costToPlant RALLODS PER SQUARE MILE TO PLANT.") + } + + fun displayExitMessage() { + println() + println("GOODBYE.") + println("(IF YOU WISH TO CONTINUE THIS GAME AT A LATER DATE, ANSWER") + println("'AGAIN' WHEN ASKED IF YOU WANT INSTRUCTIONS AT THE START") + println("OF THE GAME).") + } + + fun performLandSale() { + landArea -= sellThisYear + rallods += sellThisYear * landPrice + } + + fun performPlanting() { + rallods -= moneySpentOnPlanting + } + + fun performWelfare() { + rallods -= welfareThisYear + } + + + /** + * Ask how much land we want to sell. Immediately get the money. + * The player has to do the calculations to work out how much + * money that makes. + */ + fun inputLandSale() { + do { + print("HOW MANY SQUARE MILES DO YOU WISH TO SELL TO INDUSTRY? ") + sellThisYear = numberInput() + if (sellThisYear > landArea - 1000) { + println("*** THINK AGAIN. YOU ONLY HAVE ${landArea - 1000} SQUARE MILES OF FARM LAND.") + if (!explanationOfSellingGiven) { + println() + println("(FOREIGN INDUSTRY WILL ONLY BUY FARM LAND BECAUSE") + println("FOREST LAND IS UNECONOMICAL TO STRIP MINE DUE TO TREES,") + println("THICKER TOP SOIL, ETC.)") + explanationOfSellingGiven = true + } + } + } while (sellThisYear < 0 || sellThisYear > landArea - 1000) + } + + /** + * Input the value of `welfareThisYear` + */ + fun inputWelfare() { + do { + var retry = false + print("HOW MANY RALLODS WILL YOU DISTRIBUTE AMONG YOUR COUNTRYMEN? ") + welfareThisYear = numberInput() + + if (welfareThisYear > rallods) { + println(" THINK AGAIN. YOU'VE ONLY $rallods RALLODS IN THE TREASURY") + retry = true + } + + if (welfareThisYear < 0) { + retry = true + } + } while (retry) + } + + /** + * Get the number of square miles to plant this year. + * Validate the response: + * Each countryman can only plant 2 square miles. + * You can only plant on arable land. + * You may not spend more on planting than your treasury. + */ + fun inputPlantingArea() { + if (welfareThisYear == rallods) { + plantingArea = 0 + } else { + do { + var retry = false + print("HOW MANY SQUARE MILES DO YOU WISH TO PLANT? ") + plantingArea = numberInput() + val moneySpentOnPlanting = plantingArea * costToPlant + + if (plantingArea < 0) { + retry = true + } else if (plantingArea >= 0 && plantingArea > countrymen * 2) { + println(" SORRY, BUT EACH COUNTRYMAN CAN ONLY PLANT 2 SQ. MILES.") + retry = true + } else if (plantingArea > landArea - 1000) { + println(" SORRY, BUT YOU'VE ONLY ${landArea - 1000} SQ. MILES OF FARM LAND.") + retry = true + } else if (moneySpentOnPlanting > rallods) { + println(" THINK AGAIN. YOU'VE ONLY $rallods RALLODS LEFT IN THE TREASURY.") + retry = true + } + } while (retry) + } + + } + + /** + * Enter amount for pollution control. + * Validate that this does not exceed treasury. + */ + fun inputPollutionControl() { + do { + var retry = false + print("HOW MANY RALLODS DO YOU WISH TO SPEND ON POLLUTION CONTROL? ") + moneySpentOnPollutionControl = numberInput() + + if (rallods < 0) { + retry = true + } else if (moneySpentOnPollutionControl > rallods) { + println(" THINK AGAIN. YOU ONLY HAVE $rallods RALLODS REMAINING.") + retry = true + } + + } while (retry) + } + + /** + * @return true if all data entered so far has been zero. + */ + fun zeroInput() = sellThisYear == 0 && + welfareThisYear == 0 && + plantingArea == 0 && + moneySpentOnPollutionControl == 0 + + fun simulateOneYear() { + rallods -= moneySpentOnPollutionControl + val rallodsAfterPollutionControl = rallods + + var starvationDeaths = 0 + if (welfareThisYear / 100.0 - countrymen < 0) { + + /* + Wait, WHAT? + If you spend less than 5000 rallods on welfare, no matter the current size of the + population, then you will end the game, with the game claiming that too many + people have died, without showing exactly how many have died? + + https://github.com/coding-horror/basic-computer-games/blob/main/53_King/king.bas#:~:text=1105%20IF%20I/100%3C50%20THEN%201700 + */ + if (welfareThisYear / 100.0 < 50) + throw GameEndingException.TooManyPeopleDead() + + starvationDeaths = (countrymen - (welfareThisYear / 100.0)).toInt() + println("$starvationDeaths COUNTRYMEN DIED OF STARVATION") + } + + var pollutionDeaths = (rnd * (2000 - landArea)).toInt() + if (moneySpentOnPollutionControl >= 25) { + pollutionDeaths = (pollutionDeaths / (moneySpentOnPollutionControl / 25.0)).toInt() + } + + if (pollutionDeaths > 0) { + println("$pollutionDeaths COUNTRYMEN DIED OF CARBON-MONOXIDE AND DUST INHALATION") + } + + death = pollutionDeaths + starvationDeaths + if (death > 0) { + println(" YOU WERE FORCED TO SPEND ${death * 9}") + println("RALLODS ON FUNERAL EXPENSES") + rallods -= death * 9 + } + + if (rallods < 0) { + println(" INSUFFICIENT RESERVES TO COVER COST - LAND WAS SOLD") + landArea += rallods / landPrice + rallods = 1 + } + + countrymen -= death + + val newForeigners = + if (sellThisYear > 0) { + (sellThisYear + rnd * 10.0 + rnd * 20.0).toInt() + (if (foreignWorkers <= 0) 20 else 0) + } else 0 + + /* + Immigration is calculated as + One for every thousand rallods more welfare than strictly required + minus one for every 10 starvation deaths + plus One for every 25 rallods spent on pollution control + plus one for every 50 square miles of arable land + minus one for every 2 pollution deaths + */ + val immigration = ( + (welfareThisYear / 100.0 - countrymen) / 10.0 + + moneySpentOnPollutionControl / 25.0 - + (2000 - landArea) / 50.0 - + pollutionDeaths / 2.0 + ).toInt() + println( + "$newForeigners WORKERS CAME TO THE COUNTRY AND" + + " ${abs(immigration)} COUNTRYMEN ${if (immigration < 0) "LEFT" else "CAME TO"}" + + " THE ISLAND." + ) + + countrymen += immigration + foreignWorkers += newForeigners + + /* + Crop loss is between 75% and 125% of the land sold to industry, + due to the pollution that industry causes. + Money spent on pollution control reduces pollution deaths among + the population, but does not affect crop losses. + */ + var cropLoss = ((2000 - landArea) * (rnd + 1.5) / 2.0).toInt() + val cropLossWorse = false + if (foreignWorkers > 0) + print("OF $plantingArea SQ. MILES PLANTED,") + if (plantingArea <= cropLoss) + cropLoss = plantingArea + println(" YOU HARVESTED ${plantingArea - cropLoss} SQ. MILES OF CROPS.") + + if (cropLoss > 0) { + println(" (DUE TO ${if (cropLossWorse) "INCREASED " else ""}AIR AND WATER POLLUTION FROM FOREIGN INDUSTRY)") + } + + val agriculturalIncome = ((plantingArea - cropLoss) * landPrice / 2.0).toInt() + println("MAKING $agriculturalIncome RALLODS.") + rallods += agriculturalIncome + + val v1 = (((countrymen - immigration) * 22.0) + rnd * 500).toInt() + val v2 = ((2000.0 - landArea) * 15.0).toInt() + println(" YOU MADE ${abs(v1 - v2)} RALLODS FROM TOURIST TRADE.") + if (v2 != 0 && v1 - v2 < tourists) { + print(" DECREASE BECAUSE ") + println( + when ((10 * rnd).toInt()) { + in 0..2 -> "FISH POPULATION HAS DWINDLED DUE TO WATER POLLUTION." + in 3..4 -> "AIR POLLUTION IS KILLING GAME BIRD POPULATION." + in 5..6 -> "MINERAL BATHS ARE BEING RUINED BY WATER POLLUTION." + in 7..8 -> "UNPLEASANT SMOG IS DISCOURAGING SUN BATHERS." + else -> "HOTELS ARE LOOKING SHABBY DUE TO SMOG GRIT." + } + ) + } + + /* + The original code was incorrect. + If v3 starts at 0, for example, our money doubles, when we + have already been told that "YOU MADE ${abs(v1 - v2)} RALLODS + FROM TOURIST TRADE" + + See the original code + 1450 V3=INT(A+V3) + 1451 A=INT(A+V3) + + https://github.com/coding-horror/basic-computer-games/blob/main/53_King/king.bas#:~:text=1450%20V3%3DINT,INT(A%2BV3) + */ + if (INCLUDE_BUGS_FROM_ORIGINAL) { + tourists += rallods + } else { + tourists = abs(v1 - v2) + } + rallods += tourists + + if (death > 200) + throw GameEndingException.ExtremeMismanagement(death) + if (countrymen < 343) + throw GameEndingException.TooManyPeopleDead() + if (rallodsAfterPollutionControl / 100 > 5 && death - pollutionDeaths >= 2) + throw GameEndingException.StarvationWithFullTreasury() + if (foreignWorkers > countrymen) + throw GameEndingException.AntiImmigrationRevolution() + + } +} + + +private fun numberInput() = try { + readLine()?.toInt() ?: throw EndOfInputException() +} catch (r: NumberFormatException) { + 0 +} + + + + +