Merge remote-tracking branch 'origin/main'

This commit is contained in:
Zev Spitz
2022-01-16 08:12:59 +02:00
19 changed files with 2050 additions and 22 deletions

5
.gitignore vendored
View File

@@ -2,9 +2,8 @@
.vscode/
.gradle/
node_modules/
buildJvm/
build.gradle
buildJvm/bin
buildJvm/*/build/
.classpath
.project

View 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">- + ^ * / = &lt;&gt; &lt; &gt; &lt;= &gt;=</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&quot; 01 02&quot; 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>

View 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
View 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

View File

@@ -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
View File

@@ -0,0 +1,3 @@
*.class
*.iml
.idea/

View 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) {
}
}

View File

@@ -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.

View 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

View 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("> ");
}
}
}

View File

@@ -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
View 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
View 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
View 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;
}

View File

@@ -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(" "); }

View 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()

View File

@@ -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):