mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-02-04 19:12:07 -08:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -2,9 +2,8 @@
|
||||
.vscode/
|
||||
.gradle/
|
||||
node_modules/
|
||||
buildJvm/
|
||||
|
||||
build.gradle
|
||||
buildJvm/bin
|
||||
buildJvm/*/build/
|
||||
|
||||
.classpath
|
||||
.project
|
||||
|
||||
64
00_Utilities/VintageBASIC.xml
Normal file
64
00_Utilities/VintageBASIC.xml
Normal file
@@ -0,0 +1,64 @@
|
||||
<NotepadPlus>
|
||||
<UserLang name="Vintage BASIC" ext="bas" udlVersion="2.1">
|
||||
<Settings>
|
||||
<Global caseIgnored="no" allowFoldOfComments="no" foldCompact="no" forcePureLC="0" decimalSeparator="0" />
|
||||
<Prefix Keywords1="no" Keywords2="no" Keywords3="no" Keywords4="no" Keywords5="no" Keywords6="no" Keywords7="no" Keywords8="no" />
|
||||
</Settings>
|
||||
<KeywordLists>
|
||||
<Keywords name="Comments">00REM 01 02 03 04</Keywords>
|
||||
<Keywords name="Numbers, prefix1"></Keywords>
|
||||
<Keywords name="Numbers, prefix2"></Keywords>
|
||||
<Keywords name="Numbers, extras1"></Keywords>
|
||||
<Keywords name="Numbers, extras2"></Keywords>
|
||||
<Keywords name="Numbers, suffix1"></Keywords>
|
||||
<Keywords name="Numbers, suffix2"></Keywords>
|
||||
<Keywords name="Numbers, range"></Keywords>
|
||||
<Keywords name="Operators1">- + ^ * / = <> < > <= >=</Keywords>
|
||||
<Keywords name="Operators2"></Keywords>
|
||||
<Keywords name="Folders in code1, open"></Keywords>
|
||||
<Keywords name="Folders in code1, middle"></Keywords>
|
||||
<Keywords name="Folders in code1, close"></Keywords>
|
||||
<Keywords name="Folders in code2, open"></Keywords>
|
||||
<Keywords name="Folders in code2, middle"></Keywords>
|
||||
<Keywords name="Folders in code2, close"></Keywords>
|
||||
<Keywords name="Folders in comment, open"></Keywords>
|
||||
<Keywords name="Folders in comment, middle"></Keywords>
|
||||
<Keywords name="Folders in comment, close"></Keywords>
|
||||
<Keywords name="Keywords1">DATA DEF FN DIM END FOR GOSUB GOTO IF THEN INPUT LET NEXT ON PRINT RANDOMIZE REM RESTORE RETURN STOP TO</Keywords>
|
||||
<Keywords name="Keywords2">ABS ASC ATN CHR$ COS EXP INT LEFT$ LEN LOG MID$ RIGHT$ RND SGN SIN SPC SQR STR TAB TAN VAL</Keywords>
|
||||
<Keywords name="Keywords3">NOT AND OR</Keywords>
|
||||
<Keywords name="Keywords4"></Keywords>
|
||||
<Keywords name="Keywords5"></Keywords>
|
||||
<Keywords name="Keywords6"></Keywords>
|
||||
<Keywords name="Keywords7"></Keywords>
|
||||
<Keywords name="Keywords8"></Keywords>
|
||||
<Keywords name="Delimiters">00" 01 02" 03( 04 05) 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23</Keywords>
|
||||
</KeywordLists>
|
||||
<Styles>
|
||||
<WordsStyle name="DEFAULT" fgColor="000000" bgColor="FFFFFF" fontName="Courier New" fontStyle="0" fontSize="14" nesting="0" />
|
||||
<WordsStyle name="COMMENTS" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="LINE COMMENTS" fgColor="00FF00" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="NUMBERS" fgColor="FF0000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="KEYWORDS1" fgColor="8000FF" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="KEYWORDS2" fgColor="0080C0" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="KEYWORDS3" fgColor="800000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="KEYWORDS4" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="KEYWORDS5" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="KEYWORDS6" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="KEYWORDS7" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="KEYWORDS8" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="OPERATORS" fgColor="800000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="FOLDER IN CODE1" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="FOLDER IN CODE2" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="FOLDER IN COMMENT" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="DELIMITERS1" fgColor="0000FF" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="DELIMITERS2" fgColor="FF8040" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="DELIMITERS3" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="DELIMITERS4" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="DELIMITERS5" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="DELIMITERS6" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="DELIMITERS7" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
<WordsStyle name="DELIMITERS8" fgColor="000000" bgColor="FFFFFF" fontStyle="0" nesting="0" />
|
||||
</Styles>
|
||||
</UserLang>
|
||||
</NotepadPlus>
|
||||
189
12_Bombs_Away/python/bombs_away.py
Normal file
189
12_Bombs_Away/python/bombs_away.py
Normal file
@@ -0,0 +1,189 @@
|
||||
"""
|
||||
Bombs away
|
||||
|
||||
Ported from BASIC to Python3 by Bernard Cooke (bernardcooke53)
|
||||
Tested with Python 3.8.10, formatted with Black and type checked with mypy.
|
||||
"""
|
||||
import random
|
||||
from typing import Iterable
|
||||
|
||||
|
||||
def _stdin_choice(prompt: str, *, choices: Iterable[str]) -> str:
|
||||
ret = input(prompt)
|
||||
while ret not in choices:
|
||||
print("TRY AGAIN...")
|
||||
ret = input(prompt)
|
||||
return ret
|
||||
|
||||
|
||||
def player_survived() -> None:
|
||||
print("YOU MADE IT THROUGH TREMENDOUS FLAK!!")
|
||||
|
||||
|
||||
def player_death() -> None:
|
||||
print("* * * * BOOM * * * *")
|
||||
print("YOU HAVE BEEN SHOT DOWN.....")
|
||||
print("DEARLY BELOVED, WE ARE GATHERED HERE TODAY TO PAY OUR")
|
||||
print("LAST TRIBUTE...")
|
||||
|
||||
|
||||
def mission_success() -> None:
|
||||
print(f"DIRECT HIT!!!! {int(100 * random.random())} KILLED.")
|
||||
print("MISSION SUCCESSFUL.")
|
||||
|
||||
|
||||
def death_with_chance(p_death: float) -> bool:
|
||||
"""
|
||||
Takes a float between 0 and 1 and returns a boolean
|
||||
if the player has survived (based on random chance)
|
||||
|
||||
Returns True if death, False if survived
|
||||
"""
|
||||
return p_death > random.random()
|
||||
|
||||
|
||||
def commence_non_kamikazi_attack() -> None:
|
||||
while True:
|
||||
try:
|
||||
nmissions = int(input("HOW MANY MISSIONS HAVE YOU FLOWN? "))
|
||||
|
||||
while nmissions >= 160:
|
||||
print("MISSIONS, NOT MILES...")
|
||||
print("150 MISSIONS IS HIGH EVEN FOR OLD-TIMERS")
|
||||
nmissions = int(input("NOW THEN, HOW MANY MISSIONS HAVE YOU FLOWN? "))
|
||||
break
|
||||
except ValueError:
|
||||
# In the BASIC implementation this
|
||||
# wasn't accounted for
|
||||
print("TRY AGAIN...")
|
||||
continue
|
||||
|
||||
if nmissions >= 100:
|
||||
print("THAT'S PUSHING THE ODDS!")
|
||||
|
||||
if nmissions < 25:
|
||||
print("FRESH OUT OF TRAINING, EH?")
|
||||
|
||||
print()
|
||||
return (
|
||||
mission_success() if nmissions >= 160 * random.random() else mission_failure()
|
||||
)
|
||||
|
||||
|
||||
def mission_failure() -> None:
|
||||
weapons_choices = {
|
||||
"1": "GUNS",
|
||||
"2": "MISSILES",
|
||||
"3": "BOTH",
|
||||
}
|
||||
print(f"MISSED TARGET BY {int(2 + 30 * random.random())} MILES!")
|
||||
print("NOW YOU'RE REALLY IN FOR IT !!")
|
||||
print()
|
||||
enemy_weapons = _stdin_choice(
|
||||
prompt="DOES THE ENEMY HAVE GUNS(1), MISSILES(2), OR BOTH(3)? ",
|
||||
choices=weapons_choices,
|
||||
)
|
||||
|
||||
# If there are no gunners (i.e. weapon choice 2) then
|
||||
# we say that the gunners have 0 accuracy for the purposes
|
||||
# of calculating probability of player death
|
||||
|
||||
enemy_gunner_accuracy = 0.0
|
||||
if enemy_weapons != "2":
|
||||
# If the enemy has guns, how accurate are the gunners?
|
||||
while True:
|
||||
try:
|
||||
enemy_gunner_accuracy = float(
|
||||
input("WHAT'S THE PERCENT HIT RATE OF ENEMY GUNNERS (10 TO 50)? ")
|
||||
)
|
||||
break
|
||||
except ValueError:
|
||||
# In the BASIC implementation this
|
||||
# wasn't accounted for
|
||||
print("TRY AGAIN...")
|
||||
continue
|
||||
|
||||
if enemy_gunner_accuracy < 10:
|
||||
print("YOU LIE, BUT YOU'LL PAY...")
|
||||
return player_death()
|
||||
|
||||
missile_threat_weighting = 0 if enemy_weapons == "1" else 35
|
||||
|
||||
death = death_with_chance(
|
||||
p_death=(enemy_gunner_accuracy + missile_threat_weighting) / 100
|
||||
)
|
||||
|
||||
return player_survived() if not death else player_death()
|
||||
|
||||
|
||||
def play_italy() -> None:
|
||||
targets_to_messages = {
|
||||
# 1 - ALBANIA, 2 - GREECE, 3 - NORTH AFRICA
|
||||
"1": "SHOULD BE EASY -- YOU'RE FLYING A NAZI-MADE PLANE.",
|
||||
"2": "BE CAREFUL!!!",
|
||||
"3": "YOU'RE GOING FOR THE OIL, EH?",
|
||||
}
|
||||
target = _stdin_choice(
|
||||
prompt="YOUR TARGET -- ALBANIA(1), GREECE(2), NORTH AFRICA(3)",
|
||||
choices=targets_to_messages,
|
||||
)
|
||||
|
||||
print(targets_to_messages[target])
|
||||
return commence_non_kamikazi_attack()
|
||||
|
||||
|
||||
def play_allies() -> None:
|
||||
aircraft_to_message = {
|
||||
"1": "YOU'VE GOT 2 TONS OF BOMBS FLYING FOR PLOESTI.",
|
||||
"2": "YOU'RE DUMPING THE A-BOMB ON HIROSHIMA.",
|
||||
"3": "YOU'RE CHASING THE BISMARK IN THE NORTH SEA.",
|
||||
"4": "YOU'RE BUSTING A GERMAN HEAVY WATER PLANT IN THE RUHR.",
|
||||
}
|
||||
aircraft = _stdin_choice(
|
||||
prompt="AIRCRAFT -- LIBERATOR(1), B-29(2), B-17(3), LANCASTER(4): ",
|
||||
choices=aircraft_to_message,
|
||||
)
|
||||
|
||||
print(aircraft_to_message[aircraft])
|
||||
return commence_non_kamikazi_attack()
|
||||
|
||||
|
||||
def play_japan() -> None:
|
||||
print("YOU'RE FLYING A KAMIKAZE MISSION OVER THE USS LEXINGTON.")
|
||||
first_mission = input("YOUR FIRST KAMIKAZE MISSION? (Y OR N): ")
|
||||
if first_mission.lower() == "n":
|
||||
return player_death()
|
||||
return mission_success() if random.random() > 0.65 else player_death()
|
||||
|
||||
|
||||
def play_germany() -> None:
|
||||
targets_to_messages = {
|
||||
# 1 - RUSSIA, 2 - ENGLAND, 3 - FRANCE
|
||||
"1": "YOU'RE NEARING STALINGRAD.",
|
||||
"2": "NEARING LONDON. BE CAREFUL, THEY'VE GOT RADAR.",
|
||||
"3": "NEARING VERSAILLES. DUCK SOUP. THEY'RE NEARLY DEFENSELESS.",
|
||||
}
|
||||
target = _stdin_choice(
|
||||
prompt="A NAZI, EH? OH WELL. ARE YOU GOING FOR RUSSIA(1),\nENGLAND(2), OR FRANCE(3)? ",
|
||||
choices=targets_to_messages,
|
||||
)
|
||||
|
||||
print(targets_to_messages[target])
|
||||
|
||||
return commence_non_kamikazi_attack()
|
||||
|
||||
|
||||
def play_game() -> None:
|
||||
print("YOU ARE A PILOT IN A WORLD WAR II BOMBER.")
|
||||
sides = {"1": play_italy, "2": play_allies, "3": play_japan, "4": play_germany}
|
||||
side = _stdin_choice(
|
||||
prompt="WHAT SIDE -- ITALY(1), ALLIES(2), JAPAN(3), GERMANY(4): ", choices=sides
|
||||
)
|
||||
return sides[side]()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
again = True
|
||||
while again:
|
||||
play_game()
|
||||
again = True if input("ANOTHER MISSION? (Y OR N): ").upper() == "Y" else False
|
||||
98
25_Chief/ruby/chief.rb
Normal file
98
25_Chief/ruby/chief.rb
Normal file
@@ -0,0 +1,98 @@
|
||||
def tab(size)
|
||||
str = ''
|
||||
size.times do
|
||||
str += ' '
|
||||
end
|
||||
|
||||
str
|
||||
end
|
||||
|
||||
def input
|
||||
gets.chomp
|
||||
end
|
||||
|
||||
def bye
|
||||
print "BYE!!!\n"
|
||||
end
|
||||
|
||||
def main
|
||||
print tab(30), "CHIEF\n"
|
||||
print tab(15), "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n"
|
||||
print "\n"
|
||||
print "\n"
|
||||
print "\n"
|
||||
print "I AM CHIEF NUMBERS FREEK, THE GREAT INDIAN MATH GOD.\n"
|
||||
print "ARE YOU READY TO TAKE THE TEST YOU CALLED ME OUT FOR\n"
|
||||
|
||||
a = input
|
||||
if a != 'YES'
|
||||
print "SHUT UP, PALE FACE WITH WISE TONGUE.\n"
|
||||
end
|
||||
|
||||
print " TAKE A NUMBER AND ADD 3. DIVIDE THIS NUMBER BY 5 AND\n"
|
||||
print "MULTIPLY BY 8. DIVIDE BY 5 AND ADD THE SAME. SUBTRACT 1.\n"
|
||||
print " WHAT DO YOU HAVE\n"
|
||||
|
||||
b = input.to_f
|
||||
c = ((b + 1 - 5) * 5 / 8 * 5 -3).to_f
|
||||
print "I BET YOUR NUMBER WAS #{c}. AM I RIGHT\n"
|
||||
|
||||
d = input
|
||||
if d == 'YES'
|
||||
return bye
|
||||
end
|
||||
|
||||
print "WHAT WAS YOUR ORIGINAL NUMBER\n"
|
||||
|
||||
k = input.to_f
|
||||
f = k + 3
|
||||
g = f / 5
|
||||
h = g * 8
|
||||
i = h / 5 + 5
|
||||
j = i - 1
|
||||
|
||||
print "SO YOU THINK YOU'RE SO SMART, EH?\n"
|
||||
print "NOW WATCH.\n"
|
||||
print k, " PLUS 3 EQUALS ", f, ". THIS DIVIDED BY 5 EQUALS ", g, ";\n"
|
||||
print "THIS TIMES 8 EQUALS ", h, ". IF WE DIVIDE BY 5 AND ADD 5,\n"
|
||||
print "WE GET ", i, ", WHICH, MINUS 1, EQUALS ", j, ".\n"
|
||||
print "NOW DO YOU BELIEVE ME\n"
|
||||
|
||||
z = input
|
||||
if z == 'YES'
|
||||
return bye
|
||||
end
|
||||
|
||||
print "YOU HAVE MADE ME MAD!!!\n"
|
||||
print "THERE MUST BE A GREAT LIGHTNING BOLT!\n"
|
||||
print "\n"
|
||||
print "\n"
|
||||
|
||||
x = 30
|
||||
while x >= 22
|
||||
print tab(x), "X X\n"
|
||||
x -= 1
|
||||
end
|
||||
|
||||
print tab(21), "X XXX\n"
|
||||
print tab(20), "X X\n"
|
||||
print tab(19), "XX X\n"
|
||||
|
||||
y = 20
|
||||
while y >= 13
|
||||
print tab(y), "X X\n"
|
||||
y -= 1
|
||||
end
|
||||
|
||||
print tab(12), "XX\n"
|
||||
print tab(11), "X\n"
|
||||
print tab(10), "*\n"
|
||||
|
||||
print "\n"
|
||||
print "#########################\n"
|
||||
print "\n"
|
||||
print "I HOPE YOU BELIEVE ME NOW, FOR YOUR SAKE!!\n"
|
||||
return bye
|
||||
end
|
||||
|
||||
main
|
||||
@@ -216,7 +216,7 @@
|
||||
2280 PRINT "CASUALTIES",C5,C6
|
||||
2290 PRINT "DESERTIONS",INT(E),INT(E2)
|
||||
2300 PRINT
|
||||
2310 IF B$ <> "YES" THEN 2350
|
||||
2310 IF B$ <> "YES" THEN 2530
|
||||
2320 PRINT "COMPARED TO THE ACTUAL CASUALTIES AT "C$
|
||||
2330 PRINT "CONFEDERATE:"INT(100*(C5/C1)+.5)"% OF THE ORIGINAL"
|
||||
2340 PRINT "UNION: "INT(100*(C6/C2)+.5)"% OF THE ORIGINAL"
|
||||
|
||||
3
27_Civil_War/java/.gitignore
vendored
Normal file
3
27_Civil_War/java/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*.class
|
||||
*.iml
|
||||
.idea/
|
||||
707
27_Civil_War/java/src/CivilWar.java
Normal file
707
27_Civil_War/java/src/CivilWar.java
Normal file
@@ -0,0 +1,707 @@
|
||||
import java.io.PrintStream;
|
||||
import java.util.InputMismatchException;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static java.util.stream.IntStream.range;
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
public class CivilWar {
|
||||
|
||||
private final PrintStream out;
|
||||
private final List<HistoricalDatum> data;
|
||||
|
||||
private final BattleResults results;
|
||||
|
||||
private BattleState currentBattle;
|
||||
private int numGenerals;
|
||||
private int battleNumber;
|
||||
private boolean wantBattleDescriptions;
|
||||
private final int[] strategies;
|
||||
|
||||
private int confedStrategy;
|
||||
private int unionStrategy;
|
||||
|
||||
private final ArmyPair<ArmyResources> resources;
|
||||
private final ArmyPair<Integer> totalExpectedCasualties;
|
||||
private final ArmyPair<Integer> totalCasualties;
|
||||
private final ArmyPair<Integer> revenue;
|
||||
private final ArmyPair<Integer> inflation;
|
||||
private final ArmyPair<Integer> totalExpenditure;
|
||||
private final ArmyPair<Integer> totalTroops;
|
||||
|
||||
private boolean excessiveConfederateLosses;
|
||||
private boolean excessiveUnionLosses;
|
||||
|
||||
private boolean confedSurrender;
|
||||
private boolean unionSurrender;
|
||||
|
||||
private final static String YES_NO_REMINDER = "(ANSWER YES OR NO)";
|
||||
private final static Predicate<String> YES_NO_CHECKER = i -> isYes(i) || isNo(i);
|
||||
|
||||
/**
|
||||
* ORIGINAL GAME DESIGN: CRAM, GOODIE, HIBBARD LEXINGTON H.S.
|
||||
* MODIFICATIONS: G. PAUL, R. HESS (TIES), 1973
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
var x = new CivilWar(System.out);
|
||||
x.showCredits();
|
||||
|
||||
// LET D=RND(-1) ???
|
||||
|
||||
System.out.print("DO YOU WANT INSTRUCTIONS? ");
|
||||
|
||||
if (isYes(inputString(YES_NO_CHECKER, YES_NO_REMINDER))) {
|
||||
x.showHelp();
|
||||
}
|
||||
|
||||
x.gameLoop();
|
||||
}
|
||||
|
||||
private void gameLoop() {
|
||||
out.println();
|
||||
out.println();
|
||||
out.println();
|
||||
out.print("ARE THERE TWO GENERALS PRESENT (ANSWER YES OR NO)? ");
|
||||
|
||||
if (isYes(inputString(YES_NO_CHECKER, YES_NO_REMINDER))) {
|
||||
this.numGenerals = 2;
|
||||
} else {
|
||||
this.numGenerals = 1;
|
||||
out.println();
|
||||
out.println("YOU ARE THE CONFEDERACY. GOOD LUCK!");
|
||||
out.println();
|
||||
}
|
||||
|
||||
out.println("SELECT A BATTLE BY TYPING A NUMBER FROM 1 TO 14 ON");
|
||||
out.println("REQUEST. TYPE ANY OTHER NUMBER TO END THE SIMULATION.");
|
||||
out.println("BUT '0' BRINGS BACK EXACT PREVIOUS BATTLE SITUATION");
|
||||
out.println("ALLOWING YOU TO REPLAY IT");
|
||||
out.println();
|
||||
out.println("NOTE: A NEGATIVE FOOD$ ENTRY CAUSES THE PROGRAM TO ");
|
||||
out.println("USE THE ENTRIES FROM THE PREVIOUS BATTLE");
|
||||
out.println();
|
||||
|
||||
out.print("AFTER REQUESTING A BATTLE, DO YOU WISH BATTLE DESCRIPTIONS (ANSWER YES OR NO)? ");
|
||||
|
||||
this.wantBattleDescriptions = isYes(inputString(YES_NO_CHECKER, YES_NO_REMINDER));
|
||||
|
||||
while (true) {
|
||||
var battle = startBattle();
|
||||
if (battle == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
this.currentBattle = battle;
|
||||
|
||||
offensiveLogic(battle.data);
|
||||
|
||||
calcLosses(battle);
|
||||
|
||||
reset();
|
||||
|
||||
if (this.confedSurrender) {
|
||||
out.println("THE CONFEDERACY HAS SURRENDERED");
|
||||
} else if (unionSurrender) { // FIXME Is this actually possible? 2850
|
||||
out.println("THE UNION HAS SURRENDERED.");
|
||||
}
|
||||
}
|
||||
|
||||
complete();
|
||||
}
|
||||
|
||||
private BattleState startBattle() {
|
||||
out.println();
|
||||
out.println();
|
||||
out.println();
|
||||
out.print("WHICH BATTLE DO YOU WISH TO SIMULATE ? ");
|
||||
|
||||
var battleNumber = inputInt(i -> i >= 1 || (i == 0 && this.currentBattle != null), i -> "BATTLE " + i + " NOT ALLOWED.");
|
||||
|
||||
if (battleNumber == 0) {
|
||||
out.println(this.currentBattle.data.name + " INSTANT REPLAY");
|
||||
return this.currentBattle;
|
||||
}
|
||||
|
||||
if (battleNumber > this.data.size()) { // TYPE ANY OTHER NUMBER TO END THE SIMULATION
|
||||
return null;
|
||||
}
|
||||
|
||||
this.battleNumber = battleNumber;
|
||||
|
||||
var battle = this.data.get(this.battleNumber - 1);
|
||||
var battleState = new BattleState(battle);
|
||||
|
||||
|
||||
excessiveConfederateLosses = false;
|
||||
|
||||
// INFLATION CALC
|
||||
// REM - ONLY IN PRINTOUT IS CONFED INFLATION = I1+15%
|
||||
inflation.confederate = 10 + (results.union - results.confederate) * 2;
|
||||
inflation.union = 10 + (results.confederate - results.union) * 2;
|
||||
|
||||
// MONEY AVAILABLE
|
||||
resources.confederate.budget = 100 * (int) Math.floor((battle.troops.confederate * (100.0 - inflation.confederate) / 2000) * (1 + (revenue.confederate - totalExpenditure.confederate) / (revenue.confederate + 1.0)) + .5);
|
||||
|
||||
// MEN AVAILABLE
|
||||
battleState.F1 = 5 * battle.troops.confederate / 6.0;
|
||||
|
||||
if (this.numGenerals == 2) {
|
||||
resources.union.budget = 100 * (int) Math.floor((battle.troops.union * (100.0 - inflation.union) / 2000) * (1 + (revenue.union - totalExpenditure.union) / (revenue.union + 1.0)) + .5);
|
||||
} else {
|
||||
resources.union.budget = 100 * (int) Math.floor(battle.troops.union * (100.0 - inflation.union) / 2000 + .5);
|
||||
}
|
||||
|
||||
out.println();
|
||||
out.println();
|
||||
out.println();
|
||||
out.println();
|
||||
out.println();
|
||||
out.println("THIS IS THE BATTLE OF " + battle.name);
|
||||
|
||||
if (this.wantBattleDescriptions) {
|
||||
for (var eachLine : battle.blurb) {
|
||||
out.println(eachLine);
|
||||
}
|
||||
}
|
||||
|
||||
out.println();
|
||||
out.println(" CONFEDERACY UNION");
|
||||
out.println("MEN " + getConfedTroops(battle) + " " + getUnionTroops(battle));
|
||||
out.println("MONEY $ " + resources.confederate.budget + " $ " + resources.union.budget);
|
||||
out.println("INFLATION " + (inflation.confederate + 15) + "% " + inflation.union + "%");
|
||||
|
||||
// ONLY IN PRINTOUT IS CONFED INFLATION = I1+15%
|
||||
// IF TWO GENERALS, INPUT CONFED. FIRST
|
||||
|
||||
var terminalInput = new Scanner(System.in);
|
||||
|
||||
for (int i = 0; i < numGenerals; i++) {
|
||||
out.println();
|
||||
|
||||
ArmyResources currentResources;
|
||||
|
||||
if (this.numGenerals == 1 || i == 0) {
|
||||
out.print("CONFEDERATE GENERAL --- ");
|
||||
currentResources = resources.confederate;
|
||||
} else {
|
||||
out.print("UNION GENERAL --- ");
|
||||
currentResources = resources.union;
|
||||
}
|
||||
|
||||
var validInputs = false;
|
||||
while (!validInputs) {
|
||||
out.println("HOW MUCH DO YOU WISH TO SPEND FOR");
|
||||
out.print("- FOOD...... ? ");
|
||||
var food = terminalInput.nextInt();
|
||||
if (food == 0) {
|
||||
if (this.revenue.confederate != 0) {
|
||||
out.println("ASSUME YOU WANT TO KEEP SAME ALLOCATIONS");
|
||||
out.println();
|
||||
}
|
||||
} else {
|
||||
currentResources.food = food;
|
||||
}
|
||||
|
||||
out.print("- SALARIES.. ? ");
|
||||
currentResources.salaries = terminalInput.nextInt();
|
||||
|
||||
out.print("- AMMUNITION ? ");
|
||||
currentResources.ammunition = terminalInput.nextInt(); // FIXME Retry if -ve
|
||||
|
||||
if (currentResources.getTotal() > currentResources.budget) {
|
||||
out.println("THINK AGAIN! YOU HAVE ONLY $" + currentResources.budget);
|
||||
} else {
|
||||
validInputs = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out.println();
|
||||
|
||||
// Record Morale
|
||||
out.println(range(0, numGenerals).mapToObj(i -> moraleForArmy(battleState, i)).collect(joining(", ")));
|
||||
|
||||
out.println();
|
||||
|
||||
return battleState;
|
||||
}
|
||||
|
||||
private int getUnionTroops(HistoricalDatum battle) {
|
||||
return (int) Math.floor(battle.troops.union * (1 + (totalExpectedCasualties.union - totalCasualties.union) / (totalTroops.union + 1.0)));
|
||||
}
|
||||
|
||||
private int getConfedTroops(HistoricalDatum battle) {
|
||||
return (int) Math.floor(battle.troops.confederate * (1 + (totalExpectedCasualties.confederate - totalCasualties.confederate) / (totalTroops.confederate + 1.0)));
|
||||
}
|
||||
|
||||
private String moraleForArmy(BattleState battleState, int armyIdx) {
|
||||
var builder = new StringBuilder();
|
||||
|
||||
ArmyResources currentResources;
|
||||
|
||||
if (this.numGenerals == 1 || armyIdx == 0) {
|
||||
builder.append("CONFEDERATE ");
|
||||
currentResources = resources.confederate;
|
||||
} else {
|
||||
builder.append("UNION ");
|
||||
currentResources = resources.union;
|
||||
}
|
||||
|
||||
// FIND MORALE
|
||||
currentResources.morale = (2 * Math.pow(currentResources.food, 2) + Math.pow(currentResources.salaries, 2)) / Math.pow(battleState.F1, 2) + 1;
|
||||
if (currentResources.morale >= 10) {
|
||||
builder.append("MORALE IS HIGH");
|
||||
} else if (currentResources.morale >= 5) {
|
||||
builder.append("MORALE IS FAIR");
|
||||
} else {
|
||||
builder.append("MORALE IS POOR");
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private enum OffensiveStatus {
|
||||
DEFENSIVE("YOU ARE ON THE DEFENSIVE"), OFFENSIVE("YOU ARE ON THE OFFENSIVE"), BOTH_OFFENSIVE("BOTH SIDES ARE ON THE OFFENSIVE");
|
||||
|
||||
private final String label;
|
||||
|
||||
OffensiveStatus(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
|
||||
private void offensiveLogic(HistoricalDatum battle) {
|
||||
out.print("CONFEDERATE GENERAL---");
|
||||
// ACTUAL OFF/DEF BATTLE SITUATION
|
||||
out.println(battle.offensiveStatus.label);
|
||||
|
||||
// CHOOSE STRATEGIES
|
||||
|
||||
if (numGenerals == 2) {
|
||||
out.print("CONFEDERATE STRATEGY ? ");
|
||||
} else {
|
||||
out.print("YOUR STRATEGY ? ");
|
||||
}
|
||||
|
||||
confedStrategy = inputInt(i -> i >= 1 && i <= 5, i -> "STRATEGY " + i + " NOT ALLOWED.");
|
||||
if (confedStrategy == 5) { // 1970
|
||||
confedSurrender = true;
|
||||
}
|
||||
|
||||
if (numGenerals == 2) {
|
||||
out.print("UNION STRATEGY ? ");
|
||||
|
||||
unionStrategy = inputInt(i -> i >= 1 && i <= 5, i -> "STRATEGY " + i + " NOT ALLOWED.");
|
||||
if (unionStrategy == 5) { // 1970
|
||||
unionSurrender = true;
|
||||
}
|
||||
} else {
|
||||
unionStrategy();
|
||||
}
|
||||
}
|
||||
|
||||
// 2070 REM : SIMULATED LOSSES-NORTH
|
||||
private UnionLosses simulateUnionLosses(HistoricalDatum battle) {
|
||||
var losses = (2.0 * battle.expectedCasualties.union / 5) * (1 + 1.0 / (2 * (Math.abs(unionStrategy - confedStrategy) + 1)));
|
||||
losses = losses * (1.28 + (5.0 * battle.troops.union / 6) / (resources.union.ammunition + 1));
|
||||
losses = Math.floor(losses * (1 + 1 / resources.union.morale) + 0.5);
|
||||
// IF LOSS > MEN PRESENT, RESCALE LOSSES
|
||||
var moraleFactor = 100 / resources.union.morale;
|
||||
|
||||
if (Math.floor(losses + moraleFactor) >= getUnionTroops(battle)) {
|
||||
losses = Math.floor(13.0 * getUnionTroops(battle) / 20);
|
||||
moraleFactor = 7 * losses / 13;
|
||||
excessiveUnionLosses = true;
|
||||
}
|
||||
|
||||
return new UnionLosses((int) losses, (int) Math.floor(moraleFactor));
|
||||
}
|
||||
|
||||
// 2170: CALCULATE SIMULATED LOSSES
|
||||
private void calcLosses(BattleState battle) {
|
||||
// 2190
|
||||
out.println();
|
||||
out.println(" CONFEDERACY UNION");
|
||||
|
||||
var C5 = (2 * battle.data.expectedCasualties.confederate / 5) * (1 + 1.0 / (2 * (Math.abs(unionStrategy - confedStrategy) + 1)));
|
||||
C5 = (int) Math.floor(C5 * (1 + 1.0 / resources.confederate.morale) * (1.28 + battle.F1 / (resources.confederate.ammunition + 1.0)) + .5);
|
||||
var E = 100 / resources.confederate.morale;
|
||||
|
||||
if (C5 + 100 / resources.confederate.morale >= battle.data.troops.confederate * (1 + (totalExpectedCasualties.confederate - totalCasualties.confederate) / (totalTroops.confederate + 1.0))) {
|
||||
C5 = (int) Math.floor(13.0 * battle.data.troops.confederate / 20 * (1 + (totalExpectedCasualties.union - totalCasualties.confederate) / (totalTroops.confederate + 1.0)));
|
||||
E = 7 * C5 / 13.0;
|
||||
excessiveConfederateLosses = true;
|
||||
}
|
||||
|
||||
///// 2270
|
||||
|
||||
final UnionLosses unionLosses;
|
||||
|
||||
if (this.numGenerals == 1) {
|
||||
unionLosses = new UnionLosses((int) Math.floor(17.0 * battle.data.expectedCasualties.union * battle.data.expectedCasualties.confederate / (C5 * 20)), (int) Math.floor(5 * resources.confederate.morale));
|
||||
} else {
|
||||
unionLosses = simulateUnionLosses(battle.data);
|
||||
}
|
||||
|
||||
out.println("CASUALTIES: " + rightAlignInt(C5) + " " + rightAlignInt(unionLosses.losses));
|
||||
out.println("DESERTIONS: " + rightAlignInt(E) + " " + rightAlignInt(unionLosses.desertions));
|
||||
out.println();
|
||||
|
||||
if (numGenerals == 2) {
|
||||
out.println("COMPARED TO THE ACTUAL CASUALTIES AT " + battle.data.name);
|
||||
out.println("CONFEDERATE: " + (int) Math.floor(100 * (C5 / (double) battle.data.expectedCasualties.confederate) + 0.5) + " % OF THE ORIGINAL");
|
||||
out.println("UNION: " + (int) Math.floor(100 * (unionLosses.losses / (double) battle.data.expectedCasualties.union) + 0.5) + " % OF THE ORIGINAL");
|
||||
|
||||
out.println();
|
||||
|
||||
// REM - 1 WHO WON
|
||||
var winner = findWinner(C5 + E, unionLosses.losses + unionLosses.desertions);
|
||||
switch (winner) {
|
||||
case UNION -> {
|
||||
out.println("THE UNION WINS " + battle.data.name);
|
||||
results.union++;
|
||||
}
|
||||
case CONFED -> {
|
||||
out.println("THE CONFEDERACY WINS " + battle.data.name);
|
||||
results.confederate++;
|
||||
}
|
||||
case INDECISIVE -> {
|
||||
out.println("BATTLE OUTCOME UNRESOLVED");
|
||||
results.indeterminate++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.println("YOUR CASUALTIES WERE " + Math.floor(100 * (C5 / (double) battle.data.expectedCasualties.confederate) + 0.5) + "% OF THE ACTUAL CASUALTIES AT " + battle.data.name);
|
||||
|
||||
// FIND WHO WON
|
||||
|
||||
if (excessiveConfederateLosses) {
|
||||
out.println("YOU LOSE " + battle.data.name);
|
||||
|
||||
if (this.battleNumber != 0) {
|
||||
results.union++;
|
||||
}
|
||||
} else {
|
||||
out.println("YOU WIN " + battle.data.name);
|
||||
// CUMULATIVE BATTLE FACTORS WHICH ALTER HISTORICAL RESOURCES AVAILABLE.IF A REPLAY DON'T UPDATE.
|
||||
results.confederate++;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.battleNumber != 0) {
|
||||
totalCasualties.confederate += (int) (C5 + E);
|
||||
totalCasualties.union += unionLosses.losses + unionLosses.desertions;
|
||||
totalExpectedCasualties.confederate += battle.data.expectedCasualties.confederate;
|
||||
totalExpectedCasualties.union += battle.data.expectedCasualties.union;
|
||||
totalExpenditure.confederate += resources.confederate.getTotal();
|
||||
totalExpenditure.union += resources.union.getTotal();
|
||||
revenue.confederate += battle.data.troops.confederate * (100 - inflation.confederate) / 20;
|
||||
revenue.union += battle.data.troops.union * (100 - inflation.union) / 20;
|
||||
totalTroops.confederate += battle.data.troops.confederate;
|
||||
totalTroops.union += battle.data.troops.union;
|
||||
|
||||
updateStrategies(this.confedStrategy);
|
||||
}
|
||||
}
|
||||
|
||||
// 2790
|
||||
private void reset() {
|
||||
excessiveConfederateLosses = excessiveUnionLosses = false;
|
||||
|
||||
out.println("---------------");
|
||||
}
|
||||
|
||||
// 2820 REM------FINISH OFF
|
||||
private void complete() {
|
||||
out.println();
|
||||
out.println();
|
||||
out.println();
|
||||
out.println();
|
||||
out.println();
|
||||
out.println();
|
||||
out.println("THE CONFEDERACY HAS WON " + results.confederate + " BATTLES AND LOST " + results.union);
|
||||
|
||||
if (this.unionStrategy == 5) {
|
||||
out.println("THE CONFEDERACY HAS WON THE WAR");
|
||||
}
|
||||
|
||||
if (this.confedStrategy == 5 || results.confederate <= results.union) {
|
||||
out.println("THE UNION HAS WON THE WAR");
|
||||
}
|
||||
|
||||
out.println();
|
||||
|
||||
// FIXME 2960 IF R1=0 THEN 3100
|
||||
|
||||
out.println("FOR THE " + results.getTotal() + " BATTLES FOUGHT (EXCLUDING RERUNS)");
|
||||
out.println(" CONFEDERACY UNION");
|
||||
out.println("HISTORICAL LOSSES " + (int) Math.floor(totalExpectedCasualties.confederate + .5) + " " + (int) Math.floor(totalExpectedCasualties.union + .5));
|
||||
out.println("SIMULATED LOSSES " + (int) Math.floor(totalCasualties.confederate + .5) + " " + (int) Math.floor(totalCasualties.union + .5));
|
||||
out.println();
|
||||
out.println(" % OF ORIGINAL " + (int) Math.floor(100 * ((double) totalCasualties.confederate / totalExpectedCasualties.confederate) + .5) + " " + (int) Math.floor(100 * ((double) totalCasualties.union / totalExpectedCasualties.union) + .5));
|
||||
|
||||
if (this.numGenerals == 1) {
|
||||
out.println();
|
||||
out.println("UNION INTELLIGENCE SUGGESTS THAT THE SOUTH USED ");
|
||||
out.println("STRATEGIES 1, 2, 3, 4 IN THE FOLLOWING PERCENTAGES");
|
||||
out.println(this.strategies[0] + "," + this.strategies[1] + "," + this.strategies[2] + "," + this.strategies[3]);
|
||||
}
|
||||
}
|
||||
|
||||
private Winner findWinner(double confLosses, double unionLosses) {
|
||||
if (this.excessiveConfederateLosses && this.excessiveUnionLosses) {
|
||||
return Winner.INDECISIVE;
|
||||
}
|
||||
|
||||
if (this.excessiveConfederateLosses) {
|
||||
return Winner.UNION;
|
||||
}
|
||||
|
||||
if (this.excessiveUnionLosses || confLosses < unionLosses) {
|
||||
return Winner.CONFED;
|
||||
}
|
||||
|
||||
if (confLosses == unionLosses) {
|
||||
return Winner.INDECISIVE;
|
||||
}
|
||||
|
||||
return Winner.UNION; // FIXME Really? 2400-2420 ?
|
||||
}
|
||||
|
||||
private enum Winner {
|
||||
CONFED, UNION, INDECISIVE
|
||||
}
|
||||
|
||||
private void unionStrategy() {
|
||||
// 3130 ... so you can only input / override Union strategy on re-run??
|
||||
if (this.battleNumber == 0) {
|
||||
out.print("UNION STRATEGY ? ");
|
||||
var terminalInput = new Scanner(System.in);
|
||||
unionStrategy = terminalInput.nextInt();
|
||||
if (unionStrategy < 0) {
|
||||
out.println("ENTER 1, 2, 3, OR 4 (USUALLY PREVIOUS UNION STRATEGY)");
|
||||
// FIXME Retry Y2 input !!!
|
||||
}
|
||||
|
||||
if (unionStrategy < 5) { // 3155
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var S0 = 0;
|
||||
var r = 100 * Math.random();
|
||||
|
||||
for (unionStrategy = 1; unionStrategy <= 4; unionStrategy++) {
|
||||
S0 += this.strategies[unionStrategy - 1];
|
||||
// IF ACTUAL STRATEGY INFO IS IN PROGRAM DATA STATEMENTS THEN R-100 IS EXTRA WEIGHT GIVEN TO THAT STATEGY.
|
||||
if (r < S0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// IF ACTUAL STRAT. IN,THEN HERE IS Y2= HIST. STRAT.
|
||||
out.println("UNION STRATEGY IS " + unionStrategy);
|
||||
}
|
||||
|
||||
public CivilWar(PrintStream out) {
|
||||
this.out = out;
|
||||
|
||||
this.results = new BattleResults();
|
||||
|
||||
this.totalCasualties = new ArmyPair<>(0, 0);
|
||||
this.totalExpectedCasualties = new ArmyPair<>(0, 0);
|
||||
this.totalExpenditure = new ArmyPair<>(0, 0);
|
||||
this.totalTroops = new ArmyPair<>(0, 0);
|
||||
|
||||
this.revenue = new ArmyPair<>(0, 0);
|
||||
this.inflation = new ArmyPair<>(0, 0);
|
||||
|
||||
this.resources = new ArmyPair<>(new ArmyResources(), new ArmyResources());
|
||||
|
||||
// UNION INFO ON LIKELY CONFEDERATE STRATEGY
|
||||
this.strategies = new int[]{25, 25, 25, 25};
|
||||
|
||||
// READ HISTORICAL DATA.
|
||||
// HISTORICAL DATA...CAN ADD MORE (STRAT.,ETC) BY INSERTING DATA STATEMENTS AFTER APPRO. INFO, AND ADJUSTING READ
|
||||
this.data = List.of(
|
||||
new HistoricalDatum("BULL RUN", new ArmyPair<>(18000, 18500), new ArmyPair<>(1967, 2708), OffensiveStatus.DEFENSIVE, new String[]{"JULY 21, 1861. GEN. BEAUREGARD, COMMANDING THE SOUTH, MET", "UNION FORCES WITH GEN. MCDOWELL IN A PREMATURE BATTLE AT", "BULL RUN. GEN. JACKSON HELPED PUSH BACK THE UNION ATTACK."}),
|
||||
new HistoricalDatum("SHILOH", new ArmyPair<>(40000, 44894), new ArmyPair<>(10699, 13047), OffensiveStatus.OFFENSIVE, new String[]{"APRIL 6-7, 1862. THE CONFEDERATE SURPRISE ATTACK AT", "SHILOH FAILED DUE TO POOR ORGANIZATION."}),
|
||||
new HistoricalDatum("SEVEN DAYS", new ArmyPair<>(95000, 115000), new ArmyPair<>(20614, 15849), OffensiveStatus.OFFENSIVE, new String[]{"JUNE 25-JULY 1, 1862. GENERAL LEE (CSA) UPHELD THE", "OFFENSIVE THROUGHOUT THE BATTLE AND FORCED GEN. MCCLELLAN", "AND THE UNION FORCES AWAY FROM RICHMOND."}),
|
||||
new HistoricalDatum("SECOND BULL RUN", new ArmyPair<>(54000, 63000), new ArmyPair<>(10000, 14000), OffensiveStatus.BOTH_OFFENSIVE, new String[]{"AUG 29-30, 1862. THE COMBINED CONFEDERATE FORCES UNDER", " LEE", "AND JACKSON DROVE THE UNION FORCES BACK INTO WASHINGTON."}),
|
||||
new HistoricalDatum("ANTIETAM", new ArmyPair<>(40000, 50000), new ArmyPair<>(10000, 12000), OffensiveStatus.OFFENSIVE, new String[]{"SEPT 17, 1862. THE SOUTH FAILED TO INCORPORATE MARYLAND", "INTO THE CONFEDERACY."}),
|
||||
new HistoricalDatum("FREDERICKSBURG", new ArmyPair<>(75000, 120000), new ArmyPair<>(5377, 12653), OffensiveStatus.DEFENSIVE, new String[]{"DEC 13, 1862. THE CONFEDERACY UNDER LEE SUCCESSFULLY", "REPULSED AN ATTACK BY THE UNION UNDER GEN. BURNSIDE."}),
|
||||
new HistoricalDatum("MURFREESBORO", new ArmyPair<>(38000, 45000), new ArmyPair<>(11000, 12000), OffensiveStatus.DEFENSIVE, new String[]{"DEC 31, 1862. THE SOUTH UNDER GEN. BRAGG WON A CLOSE BATTLE."}),
|
||||
new HistoricalDatum("CHANCELLORSVILLE", new ArmyPair<>(32000, 90000), new ArmyPair<>(13000, 17197), OffensiveStatus.BOTH_OFFENSIVE, new String[]{"MAY 1-6, 1863. THE SOUTH HAD A COSTLY VICTORY AND LOST", "ONE OF THEIR OUTSTANDING GENERALS, 'STONEWALL' JACKSON."}),
|
||||
new HistoricalDatum("VICKSBURG", new ArmyPair<>(50000, 70000), new ArmyPair<>(12000, 19000), OffensiveStatus.DEFENSIVE, new String[]{"JULY 4, 1863. VICKSBURG WAS A COSTLY DEFEAT FOR THE SOUTH", "BECAUSE IT GAVE THE UNION ACCESS TO THE MISSISSIPPI."}),
|
||||
new HistoricalDatum("GETTYSBURG", new ArmyPair<>(72500, 85000), new ArmyPair<>(20000, 23000), OffensiveStatus.OFFENSIVE, new String[]{"JULY 1-3, 1863. A SOUTHERN MISTAKE BY GEN. LEE AT GETTYSBURG", "COST THEM ONE OF THE MOST CRUCIAL BATTLES OF THE WAR."}),
|
||||
new HistoricalDatum("CHICKAMAUGA", new ArmyPair<>(66000, 60000), new ArmyPair<>(18000, 16000), OffensiveStatus.BOTH_OFFENSIVE, new String[]{"SEPT. 15, 1863. CONFUSION IN A FOREST NEAR CHICKAMAUGA LED", "TO A COSTLY SOUTHERN VICTORY."}),
|
||||
new HistoricalDatum("CHATTANOOGA", new ArmyPair<>(37000, 60000), new ArmyPair<>(36700, 5800), OffensiveStatus.BOTH_OFFENSIVE, new String[]{"NOV. 25, 1863. AFTER THE SOUTH HAD SIEGED GEN. ROSENCRANS'", "ARMY FOR THREE MONTHS, GEN. GRANT BROKE THE SIEGE."}),
|
||||
new HistoricalDatum("SPOTSYLVANIA", new ArmyPair<>(62000, 110000), new ArmyPair<>(17723, 18000), OffensiveStatus.BOTH_OFFENSIVE, new String[]{"MAY 5, 1864. GRANT'S PLAN TO KEEP LEE ISOLATED BEGAN TO", "FAIL HERE, AND CONTINUED AT COLD HARBOR AND PETERSBURG."}),
|
||||
new HistoricalDatum("ATLANTA", new ArmyPair<>(65000, 100000), new ArmyPair<>(8500, 3700), OffensiveStatus.DEFENSIVE, new String[]{"AUGUST, 1864. SHERMAN AND THREE VETERAN ARMIES CONVERGED", "ON ATLANTA AND DEALT THE DEATH BLOW TO THE CONFEDERACY."})
|
||||
);
|
||||
}
|
||||
|
||||
private void showCredits() {
|
||||
out.println(" ".repeat(26) + "CIVIL WAR");
|
||||
out.println(" ".repeat(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
|
||||
out.println();
|
||||
out.println();
|
||||
out.println();
|
||||
}
|
||||
|
||||
private void updateStrategies(int strategy) {
|
||||
// REM LEARN PRESENT STRATEGY, START FORGETTING OLD ONES
|
||||
// REM - PRESENT STRATEGY OF SOUTH GAINS 3*S, OTHERS LOSE S
|
||||
// REM PROBABILITY POINTS, UNLESS A STRATEGY FALLS BELOW 5%.
|
||||
|
||||
var S = 3;
|
||||
var S0 = 0;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (this.strategies[i] <= 5) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.strategies[i] -= S;
|
||||
S0 += S;
|
||||
|
||||
}
|
||||
this.strategies[strategy - 1] += S0;
|
||||
|
||||
}
|
||||
|
||||
private void showHelp() {
|
||||
out.println();
|
||||
out.println();
|
||||
out.println();
|
||||
out.println();
|
||||
out.println("THIS IS A CIVIL WAR SIMULATION.");
|
||||
out.println("TO PLAY TYPE A RESPONSE WHEN THE COMPUTER ASKS.");
|
||||
out.println("REMEMBER THAT ALL FACTORS ARE INTERRELATED AND THAT YOUR");
|
||||
out.println("RESPONSES COULD CHANGE HISTORY. FACTS AND FIGURES USED ARE");
|
||||
out.println("BASED ON THE ACTUAL OCCURRENCE. MOST BATTLES TEND TO RESULT");
|
||||
out.println("AS THEY DID IN THE CIVIL WAR, BUT IT ALL DEPENDS ON YOU!!");
|
||||
out.println();
|
||||
out.println("THE OBJECT OF THE GAME IS TO WIN AS MANY BATTLES AS ");
|
||||
out.println("POSSIBLE.");
|
||||
out.println();
|
||||
out.println("YOUR CHOICES FOR DEFENSIVE STRATEGY ARE:");
|
||||
out.println(" (1) ARTILLERY ATTACK");
|
||||
out.println(" (2) FORTIFICATION AGAINST FRONTAL ATTACK");
|
||||
out.println(" (3) FORTIFICATION AGAINST FLANKING MANEUVERS");
|
||||
out.println(" (4) FALLING BACK");
|
||||
out.println(" YOUR CHOICES FOR OFFENSIVE STRATEGY ARE:");
|
||||
out.println(" (1) ARTILLERY ATTACK");
|
||||
out.println(" (2) FRONTAL ATTACK");
|
||||
out.println(" (3) FLANKING MANEUVERS");
|
||||
out.println(" (4) ENCIRCLEMENT");
|
||||
out.println("YOU MAY SURRENDER BY TYPING A '5' FOR YOUR STRATEGY.");
|
||||
}
|
||||
|
||||
private static final int MAX_NUM_LENGTH = 6;
|
||||
|
||||
private String rightAlignInt(int number) {
|
||||
var s = String.valueOf(number);
|
||||
return " ".repeat(MAX_NUM_LENGTH - s.length()) + s;
|
||||
}
|
||||
|
||||
private String rightAlignInt(double number) {
|
||||
return rightAlignInt((int) Math.floor(number));
|
||||
}
|
||||
|
||||
private static String inputString(Predicate<String> validator, String reminder) {
|
||||
while (true) {
|
||||
try {
|
||||
var input = new Scanner(System.in).nextLine();
|
||||
if (validator.test(input)) {
|
||||
return input;
|
||||
}
|
||||
} catch (InputMismatchException e) {
|
||||
// Ignore
|
||||
}
|
||||
System.out.println(reminder);
|
||||
}
|
||||
}
|
||||
|
||||
private static int inputInt(Predicate<Integer> validator, Function<Integer, String> reminder) {
|
||||
while (true) {
|
||||
try {
|
||||
var input = new Scanner(System.in).nextInt();
|
||||
if (validator.test(input)) {
|
||||
return input;
|
||||
}
|
||||
System.out.println(reminder.apply(input));
|
||||
} catch (InputMismatchException e) {
|
||||
System.out.println(reminder.apply(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isYes(String s) {
|
||||
if (s == null) {
|
||||
return false;
|
||||
}
|
||||
var uppercase = s.toUpperCase();
|
||||
return uppercase.equals("Y") || uppercase.equals("YES");
|
||||
}
|
||||
|
||||
private static boolean isNo(String s) {
|
||||
if (s == null) {
|
||||
return false;
|
||||
}
|
||||
var uppercase = s.toUpperCase();
|
||||
return uppercase.equals("N") || uppercase.equals("NO");
|
||||
}
|
||||
|
||||
private static class BattleState {
|
||||
private final HistoricalDatum data;
|
||||
private double F1;
|
||||
|
||||
public BattleState(HistoricalDatum data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ArmyPair<T> {
|
||||
private T confederate;
|
||||
private T union;
|
||||
|
||||
public ArmyPair(T confederate, T union) {
|
||||
this.confederate = confederate;
|
||||
this.union = union;
|
||||
}
|
||||
}
|
||||
|
||||
private static class BattleResults {
|
||||
private int confederate;
|
||||
private int union;
|
||||
private int indeterminate;
|
||||
|
||||
public int getTotal() {
|
||||
return confederate + union + indeterminate;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ArmyResources {
|
||||
private int food;
|
||||
private int salaries;
|
||||
private int ammunition;
|
||||
private int budget;
|
||||
|
||||
private double morale; // TODO really here?
|
||||
|
||||
public int getTotal() {
|
||||
return this.food + this.salaries + this.ammunition;
|
||||
}
|
||||
}
|
||||
|
||||
private record HistoricalDatum(String name, ArmyPair<Integer> troops,
|
||||
ArmyPair<Integer> expectedCasualties,
|
||||
OffensiveStatus offensiveStatus, String[] blurb) {
|
||||
}
|
||||
|
||||
private record UnionLosses(int losses, int desertions) {
|
||||
}
|
||||
}
|
||||
@@ -17,3 +17,211 @@ As published in Basic Computer Games (1978):
|
||||
|
||||
Downloaded from Vintage Basic at
|
||||
http://www.vintage-basic.net/games.html
|
||||
|
||||
### Comments on the BASIC code for re-implementers.
|
||||
|
||||
15 LET R=0
|
||||
`R` is a variable that tracks winnings and losings. Unlike other games that
|
||||
start out with a lump sum of cash to spend this game assumes the user has as
|
||||
much money as they want and we only track how much they lost or won.
|
||||
|
||||
21 LET T=1
|
||||
22 PRINT "PICK A NUMBER AND INPUT TO ROLL DICE";
|
||||
23 INPUT Z
|
||||
24 LET X=(RND(0))
|
||||
25 LET T =T+1
|
||||
26 IF T<=Z THEN 24
|
||||
This block of code does nothing other than try to scramble the random number
|
||||
generator. Random number generation is not random, they are generated from the
|
||||
previous generated number. Because of the slow speed of these systems back then,
|
||||
gaming random number generators was a concern, mostly for gameplay quality.
|
||||
If you could know the "seed value" to the generator then you could effectively
|
||||
know how to get the exact same dice rolls to happen and change your bet to
|
||||
maximize your winnings and minimize your losses.
|
||||
|
||||
The first reason this is an example of bad coding practice is the user is asked
|
||||
to input a number but no clue is given as to the use of this number. This number
|
||||
has no bearing on the game and as we'll see only has bearing on the internal
|
||||
implementation of somehow trying to get an un-game-able seed for the random number
|
||||
generator (since all future random numbers generated are based off this seed value.)
|
||||
|
||||
The `RND(1)` command generates a number from a seed value that is always
|
||||
the same, everytime, from when the machine is booted up (old C64 behavior). In
|
||||
order to avoid the same dice rolls being generated, a special call to `RND(-TI)`
|
||||
would initialize the random generator with something else. But RND(-TI) is not
|
||||
a valid command on all systems. So `RND(0)`, which generates a random number
|
||||
from the system clock is used. But technically this could be gamed because the
|
||||
system clock was driven by the bootup time, there wasn't a BIOS battery on these
|
||||
systems that kept an internal real time clock going even when the system was
|
||||
turned off, unlike your regular PC. Therefore, in order to ensure as true
|
||||
randomness as possible, insert human reaction time by asking for human input.
|
||||
|
||||
But a human could just be holding down the enter key on bootup and that would
|
||||
just skip any kind of multi-millisecond variance assigned by a natural human
|
||||
reaction time. So, paranoia being a great motivator, a number is asked of the
|
||||
user to avoid just holding down the enter key which negates the timing variance
|
||||
of a human reaction.
|
||||
|
||||
What comes next is a bit of nonsense. The block of code loops a counter, recalling
|
||||
the `RND(0)` function (and thus reseeding it with the system clock value)
|
||||
and then comparing the counter to the user's number input
|
||||
in order to bail out of the loop. Because the `RND(0)` function is based off the
|
||||
system clock and the loop of code has no branching other than the bailout
|
||||
condition, the loop also takes a fixed amount of time to execute, thus making
|
||||
repeated calls to `RND(0)` predictive and this scheming to get a better random
|
||||
number is pointless. Furthermore, the loop is based on the number the user inputs
|
||||
so a huge number like ten million causes a very noticable delay and leaves the
|
||||
user wondering if the program has errored. The author could have simply called
|
||||
`RND(0)` once and used a prompt that made more sense like asking for the users
|
||||
name and then using that name in the game's replies.
|
||||
|
||||
It is advised that you use whatever your languages' random number generator
|
||||
provides and simply skip trying to recreate this bit of nonsense including
|
||||
the user input.
|
||||
|
||||
27 PRINT"INPUT THE AMOUNT OF YOUR WAGER.";
|
||||
28 INPUT F
|
||||
30 PRINT "I WILL NOW THROW THE DICE"
|
||||
40 LET E=INT(7*RND(1))
|
||||
41 LET S=INT(7*RND(1))
|
||||
42 LET X=E+S
|
||||
.... a bit later ....
|
||||
60 IF X=1 THEN 40
|
||||
65 IF X=0 THEN 40
|
||||
|
||||
|
||||
`F` is a variable that represents the users wager for this betting round.
|
||||
`E` and `S` represent the two individual and random dice being rolled.
|
||||
This code is actually wrong because it returns a value between 0 and 6.
|
||||
`X` is the sum of these dice rolls. As you'll see though further down in the
|
||||
code, if `X` is zero or one it re-rolls the dice to maintain a potential
|
||||
outcome of the sum of two dice between 2 and 12. This skews the normal distribution
|
||||
of dice values to favor lower numbers because it does not consider that `E`
|
||||
could be zero and `S` could be 2 or higher. To show this skewing of values
|
||||
you can run the `distribution.bas` program which creates a histogram of the
|
||||
distribution of the bad dice throw code and proper dice throw code.
|
||||
|
||||
Here are the results:
|
||||
|
||||
DISTRIBUTION OF DICE ROLLS WITH INT(7*RND(1)) VS INT(6*RND(1)+1)
|
||||
THE INT(7*RND(1)) DISTRIBUTION:
|
||||
2 3 4 5 6 7 8 9 10 11 12
|
||||
6483 8662 10772 13232 15254 13007 10746 8878 6486 4357 2123
|
||||
THE INT(6*RND(1)+1) DISTRIBUTION
|
||||
2 3 4 5 6 7 8 9 10 11 12
|
||||
2788 5466 8363 11072 13947 16656 13884 11149 8324 5561 2790
|
||||
If the dice rolls are fair then we should see the largest occurrence be a 7 and
|
||||
the smallest should be 2 and 12. Furthermore the occurrences should be
|
||||
symetrical meaning there should be roughly the same amount of 2's as 12's, the
|
||||
same amount of 3's as 11's, 4's as 10's and so on until you reach the middle, 7.
|
||||
But notice in the skewed dice roll, 6 is the most rolled number not 7, and the
|
||||
rest of the numbers are not symetrical, there are many more 2's than 12's.
|
||||
So the lesson is test your code.
|
||||
|
||||
The proper way to model a dice throw, in almost every language is
|
||||
`INT(6*RND(1)+1)` or `INT(6*RND(1))+1`
|
||||
|
||||
SideNote: `X` was used already in the
|
||||
previous code block discussed but its value was never used. This is another
|
||||
poor coding practice: **Don't reuse variable names for different purposes.**
|
||||
|
||||
50 IF X=7 THEN 180
|
||||
55 IF X=11 THEN 180
|
||||
60 IF X=1 THEN 40
|
||||
62 IF X=2 THEN 195
|
||||
65 IF X=0 THEN 40
|
||||
70 IF X=2 THEN 200
|
||||
80 IF X=3 THEN 200
|
||||
90 IF X=12 THEN 200
|
||||
125 IF X=5 THEN 220
|
||||
130 IF X =6 THEN 220
|
||||
140 IF X=8 THEN 220
|
||||
150 IF X=9 THEN 220
|
||||
160 IF X =10 THEN 220
|
||||
170 IF X=4 THEN 220
|
||||
|
||||
This bit of code determines the routing of where to go for payout, or loss.
|
||||
Of course, line 60 and 65 are pointless as we've just shown and should be removed
|
||||
as long as the correct dice algorithm is also changed.
|
||||
|
||||
62 IF X=2 THEN 195
|
||||
....
|
||||
70 IF X=2 THEN 200
|
||||
The check for a 2 has already been made and the jump is done. Line 70 is
|
||||
therefore redundant and can be left out. The purpose of line 62 is only to
|
||||
print a special output, "SNAKE EYES!" which we'll see in the next block creates
|
||||
duplicate code.
|
||||
|
||||
Lines 125-170 are also pointlessly checked because we know previous values have
|
||||
been ruled out, only these last values must remain, and they are all going to
|
||||
the same place, line 220. Line 125-170 could have simply been replaced with
|
||||
`GOTO 220`
|
||||
|
||||
|
||||
|
||||
180 PRINT X "- NATURAL....A WINNER!!!!"
|
||||
185 PRINT X"PAYS EVEN MONEY, YOU WIN"F"DOLLARS"
|
||||
190 GOTO 210
|
||||
195 PRINT X"- SNAKE EYES....YOU LOSE."
|
||||
196 PRINT "YOU LOSE"F "DOLLARS."
|
||||
197 LET F=0-F
|
||||
198 GOTO 210
|
||||
200 PRINT X " - CRAPS...YOU LOSE."
|
||||
205 PRINT "YOU LOSE"F"DOLLARS."
|
||||
206 LET F=0-F
|
||||
210 LET R= R+F
|
||||
211 GOTO 320
|
||||
|
||||
This bit of code manages instant wins or losses due to 7,11 or 2,3,12. As
|
||||
mentioned previously, lines 196 and 197 are essentially the same as lines
|
||||
205 and 206. A simpler code would be just to jump after printing the special
|
||||
message of "SNAKE EYES!" to line 205.
|
||||
|
||||
Lines 197 and 206 just negate the wager by subtracting it from zero. Just saying
|
||||
`F = -F` would have sufficed. Line 210 updates your running total of winnings
|
||||
or losses with this bet.
|
||||
|
||||
220 PRINT X "IS THE POINT. I WILL ROLL AGAIN"
|
||||
230 LET H=INT(7*RND(1))
|
||||
231 LET Q=INT(7*RND(1))
|
||||
232 LET O=H+Q
|
||||
240 IF O=1 THEN 230
|
||||
250 IF O=7 THEN 290
|
||||
255 IF O=0 THEN 230
|
||||
|
||||
This code sets the point, the number you must re-roll to win without rolling
|
||||
a 7, the most probable number to roll. Except in this case again, it has the
|
||||
same incorrect dice rolling code and therefore 6 is the most probable number
|
||||
to roll. The concept of DRY (don't repeat yourself) is a coding practice which
|
||||
encourages non-duplication of code because if there is an error in the code, it
|
||||
can be fixed in one place and not multiple places like in this code. The scenario
|
||||
might be that a programmer sees some wrong code, fixes it, but neglects to
|
||||
consider that there might be duplicates of the same wrong code elsewhere. If
|
||||
you practice DRY then you never worry much about behaviors in your code diverging
|
||||
due to duplicate code snippets.
|
||||
|
||||
260 IF O=X THEN 310
|
||||
270 PRINT O " - NO POINT. I WILL ROLL AGAIN"
|
||||
280 GOTO 230
|
||||
290 PRINT O "- CRAPS. YOU LOSE."
|
||||
291 PRINT "YOU LOSE $"F
|
||||
292 F=0-F
|
||||
293 GOTO 210
|
||||
300 GOTO 320
|
||||
310 PRINT X"- A WINNER.........CONGRATS!!!!!!!!"
|
||||
311 PRINT X "AT 2 TO 1 ODDS PAYS YOU...LET ME SEE..."2*F"DOLLARS"
|
||||
312 LET F=2*F
|
||||
313 GOTO 210
|
||||
|
||||
This is the code to keep rolling until the point is made or a seven is rolled.
|
||||
Again we see the negated `F` wager and lose message duplicated. This code could
|
||||
have been reorganized using a subroutine, or in BASIC, the GOSUB command, but
|
||||
in your language its most likely just known as a function or method. You can
|
||||
do a `grep -r 'GOSUB'` from the root directory to see other BASIC programs in
|
||||
this set that use GOSUB.
|
||||
|
||||
The rest of the code if fairly straight forward, replay the game or end with
|
||||
a report of your winnings or losings.
|
||||
|
||||
|
||||
|
||||
|
||||
24
29_Craps/distributions.bas
Normal file
24
29_Craps/distributions.bas
Normal file
@@ -0,0 +1,24 @@
|
||||
10 PRINT "DISTRIBUTION OF DICE ROLLS WITH INT(7*RND(1)) VS INT(6*RND(1)+1)"
|
||||
20 DIM A(12)
|
||||
30 DIM B(12)
|
||||
100 FOR X = 1 TO 100000 : REM CHOOSE A LARGE NUMBER TO GET A FINER GRAINED HISTOGRAM
|
||||
140 REM GET A NUMBER FROM 0 TO 6 INCLUSIVE WITH THE INTENT TO THROW AWAY ZEROES.
|
||||
150 LET D1 = INT(7*RND(1))
|
||||
155 LET D2 = INT(7*RND(1))
|
||||
160 LET S1 = D1+D2
|
||||
165 REM IF THIS SUM IS LESS THAN TWO THEN TRY AGAIN.
|
||||
170 IF S1<2 THEN 150
|
||||
199 REM GET A NUMBER FROM 0 TO 5 THEN ADD 1 TO IT TO MAKE IT 1 TO 6
|
||||
200 LET D3 = INT(6*RND(1))+1
|
||||
210 LET D4 = INT(6*RND(1))+1
|
||||
220 LET S2 = D3+D4
|
||||
245 REM USE OUR ARRAY AS A HISTOGRAM, COUNTING EACH OCCURRENCE OF DICE ROLL
|
||||
250 A(S1) = A(S1) + 1
|
||||
260 B(S2) = B(S2) + 1
|
||||
290 NEXT X
|
||||
300 PRINT "THE INT(7*RND(1)) DISTRIBUTION:"
|
||||
310 FOR I = 2 TO 12 :PRINT I,:NEXT:PRINT
|
||||
320 FOR I = 2 TO 12 :PRINT A(I),:NEXT:PRINT
|
||||
325 PRINT "THE INT(6*RND(1)+1) DISTRIBUTION"
|
||||
330 FOR I = 2 TO 12 :PRINT I,:NEXT:PRINT
|
||||
340 FOR I = 2 TO 12 :PRINT B(I),:NEXT:PRINT
|
||||
125
29_Craps/java/src/Craps.java
Normal file
125
29_Craps/java/src/Craps.java
Normal file
@@ -0,0 +1,125 @@
|
||||
import java.util.Random;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
* Port of Craps from BASIC to Java 17.
|
||||
*/
|
||||
public class Craps {
|
||||
public static final Random random = new Random();
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("""
|
||||
CRAPS
|
||||
CREATIVE COMPUTING MORRISTOWN, NEW JERSEY
|
||||
|
||||
|
||||
2,3,12 ARE LOSERS; 4,5,6,8,9,10 ARE POINTS; 7,11 ARE NATURAL WINNERS.
|
||||
""");
|
||||
double winnings = 0.0;
|
||||
do {
|
||||
winnings = playCraps(winnings);
|
||||
} while (stillInterested(winnings));
|
||||
winningsReport(winnings);
|
||||
}
|
||||
|
||||
public static double playCraps(double winnings) {
|
||||
double wager = getWager();
|
||||
System.out.println("I WILL NOW THROW THE DICE");
|
||||
int roll = rollDice();
|
||||
double payout = switch (roll) {
|
||||
case 7, 11 -> naturalWin(roll, wager);
|
||||
case 2, 3, 12 -> lose(roll, wager);
|
||||
default -> setPoint(roll, wager);
|
||||
};
|
||||
return winnings + payout;
|
||||
}
|
||||
|
||||
public static int rollDice() {
|
||||
return random.nextInt(1, 7) + random.nextInt(1, 7);
|
||||
}
|
||||
|
||||
private static double setPoint(int point, double wager) {
|
||||
System.out.printf("%1$ d IS THE POINT. I WILL ROLL AGAIN%n",point);
|
||||
return makePoint(point, wager);
|
||||
}
|
||||
|
||||
private static double makePoint(int point, double wager) {
|
||||
int roll = rollDice();
|
||||
if (roll == 7)
|
||||
return lose(roll, wager);
|
||||
if (roll == point)
|
||||
return win(roll, wager);
|
||||
System.out.printf("%1$ d - NO POINT. I WILL ROLL AGAIN%n", roll);
|
||||
return makePoint(point, wager); // recursive
|
||||
}
|
||||
|
||||
private static double win(int roll, double wager) {
|
||||
double payout = 2 * wager;
|
||||
System.out.printf("%1$ d - A WINNER.........CONGRATS!!!!!!!!%n", roll);
|
||||
System.out.printf("%1$ d AT 2 TO 1 ODDS PAYS YOU...LET ME SEE...$%2$3.2f%n",
|
||||
roll, payout);
|
||||
return payout;
|
||||
}
|
||||
|
||||
private static double lose(int roll, double wager) {
|
||||
String msg = roll == 2 ? "SNAKE EYES.":"CRAPS";
|
||||
System.out.printf("%1$ d - %2$s...YOU LOSE.%n", roll, msg);
|
||||
System.out.printf("YOU LOSE $%3.2f%n", wager);
|
||||
return -wager;
|
||||
}
|
||||
|
||||
public static double naturalWin(int roll, double wager) {
|
||||
System.out.printf("%1$ d - NATURAL....A WINNER!!!!%n", roll);
|
||||
System.out.printf("%1$ d PAYS EVEN MONEY, YOU WIN $%2$3.2f%n", roll, wager);
|
||||
return wager;
|
||||
}
|
||||
|
||||
public static void winningsUpdate(double winnings) {
|
||||
System.out.println(switch ((int) Math.signum(winnings)) {
|
||||
case 1 -> "YOU ARE NOW AHEAD $%3.2f".formatted(winnings);
|
||||
case 0 -> "YOU ARE NOW EVEN AT 0";
|
||||
default -> "YOU ARE NOW UNDER $%3.2f".formatted(-winnings);
|
||||
});
|
||||
}
|
||||
|
||||
public static void winningsReport(double winnings) {
|
||||
System.out.println(
|
||||
switch ((int) Math.signum(winnings)) {
|
||||
case 1 -> "CONGRATULATIONS---YOU CAME OUT A WINNER. COME AGAIN!";
|
||||
case 0 -> "CONGRATULATIONS---YOU CAME OUT EVEN, NOT BAD FOR AN AMATEUR";
|
||||
default -> "TOO BAD, YOU ARE IN THE HOLE. COME AGAIN.";
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static boolean stillInterested(double winnings) {
|
||||
System.out.print(" IF YOU WANT TO PLAY AGAIN PRINT 5 IF NOT PRINT 2 ");
|
||||
int fiveOrTwo = (int)getInput();
|
||||
winningsUpdate(winnings);
|
||||
return fiveOrTwo == 5;
|
||||
}
|
||||
|
||||
public static double getWager() {
|
||||
System.out.print("INPUT THE AMOUNT OF YOUR WAGER. ");
|
||||
return getInput();
|
||||
}
|
||||
|
||||
public static double getInput() {
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
System.out.print("> ");
|
||||
while (true) {
|
||||
try {
|
||||
return scanner.nextDouble();
|
||||
} catch (Exception ex) {
|
||||
try {
|
||||
scanner.nextLine(); // flush whatever this non number stuff is.
|
||||
} catch (Exception ns_ex) { // received EOF (ctrl-d or ctrl-z if windows)
|
||||
System.out.println("END OF INPUT, STOPPING PROGRAM.");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
System.out.println("!NUMBER EXPECTED - RETRY INPUT LINE");
|
||||
System.out.print("> ");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,11 @@ function tab(space)
|
||||
return str;
|
||||
}
|
||||
|
||||
function roll()
|
||||
{
|
||||
return Math.floor(6 * Math.random())+1 + Math.floor(6 * Math.random())+1;
|
||||
}
|
||||
|
||||
// Main program
|
||||
async function main()
|
||||
{
|
||||
@@ -52,22 +57,11 @@ async function main()
|
||||
print("\n");
|
||||
r = 0;
|
||||
print("2,3,12 ARE LOSERS: 4,5,6,8,9,10 ARE POINTS: 7,11 ARE NATURAL WINNERS.\n");
|
||||
t = 1;
|
||||
print("PICK A NUMBER AND INPUT TO ROLL DICE");
|
||||
z = parseInt(await input());
|
||||
do {
|
||||
x = Math.random();
|
||||
t++;
|
||||
} while (t <= z) ;
|
||||
while (1) {
|
||||
print("INPUT THE AMOUNT OF YOUR WAGER.");
|
||||
f = parseInt(await input());
|
||||
print("I WILL NOW THROW THE DICE\n");
|
||||
do {
|
||||
e = Math.floor(7 * Math.random());
|
||||
s = Math.floor(7 * Math.random());
|
||||
x = e + s;
|
||||
} while (x == 0 || x == 1) ;
|
||||
x = roll();
|
||||
if (x == 7 || x == 11) {
|
||||
print(x + " - NATURAL....A WINNER!!!!\n");
|
||||
print(x + " PAYS EVEN MONEY, YOU WIN " + f + " DOLLARS\n");
|
||||
@@ -83,11 +77,7 @@ async function main()
|
||||
} else {
|
||||
print(x + " IS THE POINT. I WILL ROLL AGAIN\n");
|
||||
while (1) {
|
||||
do {
|
||||
h = Math.floor(7 * Math.random());
|
||||
q = Math.floor(7 * Math.random());
|
||||
o = h + q;
|
||||
} while (o == 0 || o == 1) ;
|
||||
o = roll();
|
||||
if (o == 7) {
|
||||
print(o + " - CRAPS, YOU LOSE.\n");
|
||||
print("YOU LOSE $" + f + "\n");
|
||||
|
||||
113
29_Craps/perl/craps.pl
Normal file
113
29_Craps/perl/craps.pl
Normal file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
|
||||
my $bank = 0;
|
||||
|
||||
&main;
|
||||
|
||||
sub main {
|
||||
&print_intro;
|
||||
my $continue=5;
|
||||
until ($continue != 5) {
|
||||
$continue=&game_play;
|
||||
&print_bank;
|
||||
}
|
||||
&final_bank;
|
||||
}
|
||||
|
||||
sub game_play {
|
||||
my $point = 0;
|
||||
my $continue = 1;
|
||||
print "INPUT THE AMOUNT OF YOUR WAGER.\n";
|
||||
chomp(my $wager=<STDIN>);
|
||||
print "I WILL NOW THROW THE DICE\n";
|
||||
until ($continue == 0) {
|
||||
my $roll = &roll_dice;
|
||||
$continue = &check_value($roll,$wager);
|
||||
}
|
||||
print "IF YOU WANT TO PLAY AGAIN PRINT 5 IF NOT PRINT 2\n";
|
||||
chomp(my $ans=<STDIN>);
|
||||
return $ans;
|
||||
}
|
||||
|
||||
sub print_bank {
|
||||
if ($bank < 0) {
|
||||
print "YOU ARE NOW UNDER \$$bank\n";
|
||||
}
|
||||
elsif ($bank > 0) {
|
||||
print "YOU ARE NOW AHEAD \$$bank\n";
|
||||
}
|
||||
else {
|
||||
print "YOU ARE EVEN AT 0\n";
|
||||
}
|
||||
}
|
||||
|
||||
sub final_bank {
|
||||
if ($bank < 0) {
|
||||
print "TOO BAD, YOU ARE IN THE HOLE. COME AGAIN\n";
|
||||
}
|
||||
elsif ($bank > 0) {
|
||||
print "CONGRATULATIONS---YOU CAME OUT A WINNER. COME AGAIN!\n";
|
||||
}
|
||||
else {
|
||||
print "CONGRATULATIONS---YOU CAME OUT EVEN. NOT BAD FOR AN AMATEUR!\n";
|
||||
}
|
||||
}
|
||||
|
||||
sub check_value {
|
||||
my $roll = shift;
|
||||
my $wager = shift;
|
||||
if ($roll == 7 || $roll == 11) {
|
||||
print "$roll - NATURAL....A WINNER!!!!\n";
|
||||
print "$roll PAYS EVEN MONEY, YOU WIN $wager DOLLARS\n";
|
||||
$bank += $wager;
|
||||
return 0;
|
||||
}
|
||||
elsif ($roll == 2 || $roll == 3 || $roll == 12) {
|
||||
if ($roll == 2) {
|
||||
print "$roll - SNAKE EYES....YOU LOSE.\n";
|
||||
print "YOU LOSE $wager DOLLARS.\n";
|
||||
}
|
||||
else {
|
||||
print "$roll - CRAPS...YOU LOSE.\n";
|
||||
print "YOU LOSE $wager DOLLARS.\n";
|
||||
}
|
||||
$bank -= $wager;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
my $point = $roll;
|
||||
print "$point IS THE POINT. I WILL ROLL AGAIN\n";
|
||||
until (1==2) {
|
||||
$roll = &roll_dice;
|
||||
if ($roll == 7) {
|
||||
print "$roll YOU LOSE $wager\n";
|
||||
$bank -= $wager;
|
||||
return 0;
|
||||
}
|
||||
elsif ($roll == $point) {
|
||||
print "$roll - A WINNER..........CONGRATS!!!!!!!!\n";
|
||||
my $payout = $wager * 2;
|
||||
print "$roll AT 2 TO 1 ODDS PAYS YOU...LET ME SEE...$payout DOLLARS\n";
|
||||
$bank += $payout;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
print "$roll - NO POINT. I WILL ROLL AGAIN\n";
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub roll_dice {
|
||||
my $die1 = 1+int rand(6);
|
||||
my $die2 = 1+int rand(6);
|
||||
return ($die1 + $die2);
|
||||
}
|
||||
|
||||
sub print_intro {
|
||||
print ' ' x 33 . "CRAPS\n";
|
||||
print ' ' x 15 . "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n";
|
||||
print "2,3,12 ARE LOSERS; 4,5,6,8,9,10 ARE POINTS; 7,11 ARE NATURAL WINNERS.\n";
|
||||
}
|
||||
261
30_Cube/ruby/cube.rb
Normal file
261
30_Cube/ruby/cube.rb
Normal file
@@ -0,0 +1,261 @@
|
||||
$landmines = Array.new
|
||||
|
||||
$currentLocation = "111"
|
||||
|
||||
$standings = 500 # starting amount
|
||||
|
||||
def getYesOrNoResponseTo prompt
|
||||
print prompt
|
||||
# strip leading and trailing whitespace from entry
|
||||
yesno = gets.strip.upcase[0]
|
||||
yesno == "Y"
|
||||
end
|
||||
|
||||
def greeting
|
||||
puts "CUBE".center(80)
|
||||
puts "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY".center(80)
|
||||
puts "\n\n\n"
|
||||
response = getYesOrNoResponseTo "Do you want to see the INSTRUCTIONS?"
|
||||
if response
|
||||
puts "This is a game in which you will be playing against the"
|
||||
puts "random decision of the computer. The field of play is a"
|
||||
puts "cube of size 3. Any of your 27 locations can be designated"
|
||||
puts "by inputting three numbers such as 231."
|
||||
puts "At the start you are automatically at location 1,1,1.\n"
|
||||
puts "The object of the game is to get to location 3,3,3.\n"
|
||||
|
||||
puts "\nONE MINOR DETAIL:"
|
||||
puts "The computer will pick five random locations at which it will"
|
||||
puts "plant land mines. if you hit one of these locations you lose.\n"
|
||||
|
||||
puts "\nONE OTHER DETAIL:"
|
||||
puts "You may move only one space in one direction each move."
|
||||
puts "For example: from 1,1,2 you may move to 2,1,2 or 1,1,3."
|
||||
puts "You may not change more than one number on the same move."
|
||||
puts "If you make an illegal move, you lose and the computer takes"
|
||||
puts "the money you may have bet on that round."
|
||||
puts ""
|
||||
puts "When stating the amount of a wager, enter only the number"
|
||||
puts "of dollars (example: 250) You are automatically started with"
|
||||
puts "500 dollars in your account."
|
||||
puts
|
||||
puts "Good luck!"
|
||||
end
|
||||
end
|
||||
|
||||
def landMindStringFrom x, y, z
|
||||
landMine = x.to_s + y.to_s + z.to_s
|
||||
return landMine
|
||||
end
|
||||
|
||||
def assignLandMines
|
||||
$landmines.clear
|
||||
|
||||
# put five unique entries into the landmines array
|
||||
while $landmines.size < 5 do
|
||||
a = rand(3)+1
|
||||
b = rand(3)+1
|
||||
c = rand(3)+1
|
||||
landmine = landMindStringFrom(a, b, c)
|
||||
if !$landmines.include?(landmine) && landmine != "333"
|
||||
# puts landmine # debugging
|
||||
$landmines.push landmine
|
||||
end
|
||||
end
|
||||
$currentLocation = "111"
|
||||
end
|
||||
|
||||
def initializePot
|
||||
$standings = 500 # starting amount
|
||||
end
|
||||
|
||||
def startGame
|
||||
assignLandMines
|
||||
displayStandings
|
||||
response = getYesOrNoResponseTo "WANT TO MAKE A WAGER? "
|
||||
if response
|
||||
print "HOW MUCH? "
|
||||
while true do
|
||||
wager = gets.strip.tr('^0-9', '').to_i
|
||||
if $standings < wager
|
||||
puts "TRIED TO FOOL ME; BET AGAIN ";
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
wager = 0
|
||||
end
|
||||
|
||||
# start at location 1,1,1
|
||||
$currentLocation = "111"
|
||||
return wager
|
||||
end
|
||||
|
||||
def goodbye
|
||||
puts "TOUGH LUCK!"
|
||||
puts ""
|
||||
puts "GOODBYE."
|
||||
exit
|
||||
end
|
||||
|
||||
def bust
|
||||
puts "YOU BUST."
|
||||
goodbye
|
||||
end
|
||||
|
||||
def tryAgain
|
||||
again = getYesOrNoResponseTo "WANT TO TRY AGAIN? "
|
||||
if not again
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
def isLegalMove? newLocation
|
||||
# test for illegal moves
|
||||
# can only change one variable per move
|
||||
# newLocation is the proposed new position
|
||||
# can only move one space from the current position
|
||||
|
||||
moveX = newLocation[0].to_i
|
||||
moveY = newLocation[1].to_i
|
||||
moveZ = newLocation[2].to_i
|
||||
|
||||
# currentX, currentY, currentZ contains the current position
|
||||
currentX = $currentLocation[0].to_i
|
||||
currentY = $currentLocation[1].to_i
|
||||
currentZ = $currentLocation[2].to_i
|
||||
|
||||
isLegalMove = true
|
||||
errorString = ""
|
||||
# ensure we're not moving off the cube
|
||||
if not (moveX.between?(1,3) && moveY.between?(1,3) && moveZ.between?(1,3))
|
||||
errorString = "You moved off the cube!"
|
||||
return errorString
|
||||
end
|
||||
|
||||
# test for only one move from current position
|
||||
if not moveX.between?(currentX-1,currentX+1)
|
||||
isLegalMove = false
|
||||
end
|
||||
if not moveY.between?(currentY-1,currentY+1)
|
||||
isLegalMove = false
|
||||
end
|
||||
if not moveZ.between?(currentZ-1,currentZ+1)
|
||||
isLegalMove = false
|
||||
end
|
||||
if not isLegalMove
|
||||
errorString = "You've gone too far"
|
||||
end
|
||||
|
||||
# only allow change to one variable at a time
|
||||
if isLegalMove
|
||||
if moveX != currentX
|
||||
if moveY != currentY or moveZ != currentZ
|
||||
isLegalMove = false
|
||||
end
|
||||
end
|
||||
if moveY != currentY
|
||||
if moveX != currentX or moveZ != currentZ
|
||||
isLegalMove = false
|
||||
end
|
||||
end
|
||||
if moveZ != currentZ
|
||||
if moveY != currentY or moveX != currentX
|
||||
isLegalMove = false
|
||||
end
|
||||
end
|
||||
if not isLegalMove
|
||||
errorString = "You made too many changes"
|
||||
end
|
||||
end
|
||||
|
||||
return errorString
|
||||
end
|
||||
|
||||
def displayStandings
|
||||
print "You now have " + $standings.to_s
|
||||
if $standings > 1
|
||||
puts " dollars"
|
||||
else
|
||||
puts " dollar"
|
||||
end
|
||||
end
|
||||
|
||||
def didWin? location
|
||||
location == "333"
|
||||
end
|
||||
|
||||
def youWin amount
|
||||
$standings += amount
|
||||
puts "*** You win " + amount.to_s + " dollars! ***\n\n"
|
||||
displayStandings
|
||||
tryAgain
|
||||
assignLandMines
|
||||
puts "*** new cube ***"
|
||||
puts "different landmine locations"
|
||||
puts "starting over at location 111"
|
||||
end
|
||||
|
||||
def youLose amount
|
||||
# subtract the bet amount from the standings
|
||||
if amount > 0
|
||||
puts "You lose " + amount.to_s + " dollars!\n\n"
|
||||
$standings -= amount
|
||||
end
|
||||
if $standings <= 0
|
||||
# no money left, so end the game
|
||||
bust
|
||||
else
|
||||
displayStandings
|
||||
end
|
||||
tryAgain
|
||||
$currentLocation = "111"
|
||||
puts "starting over at location 111"
|
||||
end
|
||||
|
||||
def landMine betAmount
|
||||
puts "******BANG******"
|
||||
puts "You hit a land mine at " + $currentLocation + "!"
|
||||
youLose betAmount
|
||||
end
|
||||
|
||||
def gameLoop betAmount
|
||||
while true do
|
||||
puts ""
|
||||
print "IT'S YOUR MOVE: "
|
||||
# allow only integers: strip anything else from input
|
||||
moveToLocation = gets.strip.tr('^0-9', '')
|
||||
|
||||
# test for illegal moves
|
||||
# can only change one variable per move
|
||||
# moveToLocation is the proposed new position
|
||||
|
||||
error = isLegalMove?(moveToLocation)
|
||||
if error == ""
|
||||
# assign the new position
|
||||
$currentLocation = moveToLocation
|
||||
|
||||
# test for win
|
||||
if didWin?(moveToLocation)
|
||||
youWin betAmount
|
||||
end
|
||||
|
||||
# haven't won yet, test the land mines
|
||||
if $landmines.include? moveToLocation
|
||||
landMine betAmount
|
||||
end
|
||||
|
||||
else
|
||||
puts "Illegal move: " + error
|
||||
youLose betAmount
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
greeting
|
||||
initializePot
|
||||
gameLoop startGame
|
||||
|
||||
121
40_Gomoko/perl/gomoko.pl
Normal file
121
40_Gomoko/perl/gomoko.pl
Normal file
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/perl
|
||||
use strict;
|
||||
|
||||
|
||||
print ' 'x 33 . "GOMOKO\n";
|
||||
print ' 'x 15 . "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n";
|
||||
print "\n"; print "\n"; print "\n";
|
||||
#my @A;
|
||||
print "WELCOME TO THE ORIENTAL GAME OF GOMOKO.\n";
|
||||
print "\n"; print "THE GAME IS PLAYED ON AN N BY N GRID OF A SIZE\n";
|
||||
print "THAT YOU SPECIFY. DURING YOUR PLAY, YOU MAY COVER ONE GRID\n";
|
||||
print "INTERSECTION WITH A MARKER. THE OBJECT OF THE GAME IS TO GET\n";
|
||||
print "5 ADJACENT MARKERS IN A ROW -- HORIZONTALLY, VERTICALLY, OR\n";
|
||||
print "DIAGONALLY. ON THE BOARD DIAGRAM, YOUR MOVES ARE MARKED\n";
|
||||
print "WITH A '1' AND THE COMPUTER MOVES WITH A '2'.\n";
|
||||
print "\n"; print "THE COMPUTER DOES NOT KEEP TRACK OF WHO HAS WON.\n";
|
||||
print "TO END THE GAME, TYPE -1,-1 FOR YOUR MOVE.\n"; print "\n";
|
||||
|
||||
|
||||
my $Ret;
|
||||
my $I;
|
||||
my $J;
|
||||
|
||||
my @Board;
|
||||
my $Size= 0;
|
||||
|
||||
|
||||
while (1) {
|
||||
|
||||
do {
|
||||
$Size= 0;
|
||||
print "WHAT IS YOUR BOARD SIZE (MIN 7/ MAX 19)"; print "? "; chomp($Size = uc(<STDIN>));
|
||||
if ($Size<7 || $Size>19) {
|
||||
$Size=0;
|
||||
print "I SAID, THE MINIMUM IS 7, THE MAXIMUM IS 19.\n";
|
||||
}
|
||||
} until ($Size);
|
||||
|
||||
#==> Reset Board to zeroes...
|
||||
for (my $I=1; $I<=$Size; $I++) {
|
||||
for (my $J=1; $J<=$Size; $J++) {
|
||||
$Board[$I][$J]= 0;
|
||||
}
|
||||
}
|
||||
|
||||
print "\n"; print "WE ALTERNATE MOVES. YOU GO FIRST...\n"; print "\n";
|
||||
|
||||
while (1) {
|
||||
do {
|
||||
print "YOUR PLAY (I,J)"; print "? "; chomp(my $Inp = uc(<STDIN>));
|
||||
($I, $J)= split(",", $Inp);
|
||||
print "\n";
|
||||
if ($I==-1) { last; }
|
||||
$Ret= &ValidMove($I, $J, 1);
|
||||
} until ($Ret==1);
|
||||
if ($I==-1) { last; }
|
||||
$Board[$I][$J]= 1;
|
||||
|
||||
my $X;
|
||||
my $Y;
|
||||
my $Found=0;
|
||||
# REM *** COMPUTER TRIES AN INTELLIGENT MOVE ***
|
||||
#==> Too complex, original basic code seems only move below user.
|
||||
$Ret= &ValidMove($I+1, $J);
|
||||
if ($Ret==1) {
|
||||
$Found=1;
|
||||
$X= $I+1;
|
||||
$Y= $J;
|
||||
}
|
||||
|
||||
while($Found==0) {
|
||||
# REM *** COMPUTER TRIES A RANDOM MOVE ***
|
||||
$X= int($Size*rand(1)+1);
|
||||
$Y= int($Size*rand(1)+1);
|
||||
$Ret= &ValidMove($X, $Y, 2);
|
||||
if ($Ret==1) { $Found= 1; }
|
||||
};
|
||||
$Board[$X][$Y]=2;
|
||||
|
||||
&ShowBoard();
|
||||
}
|
||||
|
||||
print "\n"; print "THANKS FOR THE GAME!!\n";
|
||||
print "PLAY AGAIN (1 FOR YES, 0 FOR NO)"; print "? "; chomp(my $Q = uc(<STDIN>));
|
||||
if ($Q==0) { last; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
exit;
|
||||
|
||||
|
||||
sub ShowBoard {
|
||||
for (my $I=1; $I<=$Size; $I++) {
|
||||
print " ";
|
||||
for (my $J=1; $J<=$Size; $J++) {
|
||||
print "$Board[$I][$J] ";
|
||||
}
|
||||
print "\n";
|
||||
}
|
||||
print "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
sub ValidMove {
|
||||
my ($X, $Y, $Val)= @_;
|
||||
if ($X<1 || $X>$Size || $Y<1 || $Y>$Size) {
|
||||
if ($Val==1) { print "ILLEGAL MOVE. TRY AGAIN...\n"; }
|
||||
return 0;
|
||||
}
|
||||
if ($Board[$X][$Y]!=0) {
|
||||
if ($Val==1) { print "SQUARE OCCUPIED. TRY AGAIN...\n"; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
#$Board[$X][$Y]= $Val;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import java.util.Arrays;
|
||||
import java.util.Scanner;
|
||||
|
||||
public class main {
|
||||
public class Name {
|
||||
|
||||
public static void printempty() { System.out.println(" "); }
|
||||
|
||||
115
67_One_Check/python/onecheck.py
Normal file
115
67_One_Check/python/onecheck.py
Normal file
@@ -0,0 +1,115 @@
|
||||
# ONE CHECK
|
||||
|
||||
# Port to python by imiro
|
||||
|
||||
def tab(x):
|
||||
return ' '*x
|
||||
|
||||
def main():
|
||||
|
||||
# Initial instructions
|
||||
print(tab(30) + "ONE CHECK");
|
||||
print(tab(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
|
||||
print();
|
||||
print();
|
||||
print();
|
||||
print("SOLITAIRE CHECKER PUZZLE BY DAVID AHL");
|
||||
print();
|
||||
print("48 CHECKERS ARE PLACED ON THE 2 OUTSIDE SPACES OF A");
|
||||
print("STANDARD 64-SQUARE CHECKERBOARD. THE OBJECT IS TO");
|
||||
print("REMOVE AS MANY CHECKERS AS POSSIBLE BY DIAGONAL JUMPS");
|
||||
print("(AS IN STANDARD CHECKERS). USE THE NUMBERED BOARD TO");
|
||||
print("INDICATE THE SQUARE YOU WISH TO JUMP FROM AND TO. ON");
|
||||
print("THE BOARD PRINTED OUT ON EACH TURN '1' INDICATES A");
|
||||
print("CHECKER AND '0' AN EMPTY SQUARE. WHEN YOU HAVE NO");
|
||||
print("POSSIBLE JUMPS REMAINING, INPUT A '0' IN RESPONSE TO");
|
||||
print("QUESTION 'JUMP FROM ?'");
|
||||
print();
|
||||
print("HERE IS THE NUMERICAL BOARD:");
|
||||
print();
|
||||
|
||||
while(True):
|
||||
for j in range(1,64,8):
|
||||
for i in range(j,j+7):
|
||||
print(i, end=(' '*(3 if i < 10 else 2)))
|
||||
print(j+7)
|
||||
print()
|
||||
print("AND HERE IS THE OPENING POSITION OF THE CHECKERS.")
|
||||
print()
|
||||
|
||||
(jumps, left) = play_game()
|
||||
|
||||
print()
|
||||
print("YOU MADE " + jumps + " JUMPS AND HAD " + left + " PIECES")
|
||||
print("REMAINING ON THE BOARD.")
|
||||
print()
|
||||
|
||||
if not(try_again()):
|
||||
break
|
||||
|
||||
print()
|
||||
print("O.K. HOPE YOU HAD FUN!!")
|
||||
|
||||
def play_game():
|
||||
# Initialize board
|
||||
# Give more than 64 elements to accomodate 1-based indexing
|
||||
board = [1]*70
|
||||
for j in range(19,44,8):
|
||||
for i in range(j,j+4):
|
||||
board[i] = 0
|
||||
jumps = 0
|
||||
while True:
|
||||
# print board
|
||||
for j in range(1,64,8):
|
||||
for i in range(j,j+7):
|
||||
print(board[i], end=' ')
|
||||
print(board[j+7])
|
||||
print()
|
||||
|
||||
while True:
|
||||
print("JUMP FROM", end=' ')
|
||||
f = input()
|
||||
f = int(f)
|
||||
if f == 0:
|
||||
break
|
||||
print("TO", end=' ')
|
||||
t = input()
|
||||
t = int(t)
|
||||
print()
|
||||
|
||||
# Check legality of move
|
||||
f1 = ((f-1) // 8)
|
||||
f2 = f - 8 * f1
|
||||
t1 = ((t-1) // 8)
|
||||
t2 = t - 8 * t1
|
||||
if (f1 > 7 or t1 > 7 or f2 > 8 or t2 > 8 or abs(f1 - t1) != 2 or
|
||||
abs(f2 - t2) != 2 or board[(t + f) // 2] == 0 or
|
||||
board[f] == 0 or board[t] == 1):
|
||||
print("ILLEGAL MOVE. TRY AGAIN...")
|
||||
continue
|
||||
break
|
||||
|
||||
if(f == 0):
|
||||
break
|
||||
board[t] = 1
|
||||
board[f] = 0
|
||||
board[(t+f) // 2] = 0
|
||||
jumps = jumps + 1
|
||||
|
||||
left = 0
|
||||
for i in range(1,64+1):
|
||||
left = left + board[i]
|
||||
return (str(jumps), str(left))
|
||||
|
||||
def try_again():
|
||||
print("TRY AGAIN", end=' ')
|
||||
answer = input()
|
||||
if (answer.upper() == "YES"):
|
||||
return True
|
||||
elif (answer.upper() == "NO"):
|
||||
return False
|
||||
print("PLEASE ANSWER 'YES' OR 'NO'.")
|
||||
try_again()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -88,6 +88,17 @@ The relation between the Historical and Standard nomenclatures is shown in the s
|
||||
|
||||
15. This version of Star Trek was created for a Data General Nova 800 system with 32K or core. So that it would fit, the instructions are separated from the main program via a CHAIN. For conversion to DEC BASIC-PLUS, Statement 160 (Randomize) should be moved after the return from the chained instructions, say to Statement 245. For Altair BASIC, Randomize and the chain instructions should be eliminated.
|
||||
|
||||
---
|
||||
#### Bugs
|
||||
|
||||
Many of the programs in this book and this collection have bugs in the original code.
|
||||
|
||||
@jkboyce has done a great job of discovering and fixing a number of bugs in the [original code](superstartrek.bas), as part of his [python implementation](python/superstartrek.py), which should be noted by other implementers:
|
||||
|
||||
- line `4410` : `D(7)` should be `D(6)`
|
||||
- lines `8310`,`8330`,`8430`,`8450` : Division by zero is possible
|
||||
- line `440` : `B9` should be initialised to 0, not 2
|
||||
|
||||
---
|
||||
|
||||
As published in Basic Computer Games (1978):
|
||||
|
||||
Reference in New Issue
Block a user