Merge branch 'coding-horror:main' into main

This commit is contained in:
JoeStrout
2023-07-06 14:47:48 -07:00
committed by GitHub
179 changed files with 7337 additions and 7 deletions
@@ -0,0 +1,3 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [C++17](https://en.wikipedia.org/wiki/C%2B%2B17)
@@ -0,0 +1,21 @@
#include <iostream> // std::cout, std::endl
#include <string> // std::string(size_t n, char c)
#include <cmath> // std::sin(double x)
int main()
{
std::cout << std::string(30, ' ') << "SINE WAVE" << std::endl;
std::cout << std::string(15, ' ') << "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY" << std::endl;
std::cout << std::string(5, '\n');
bool b = true;
for (double t = 0.0; t <= 40.0; t += 0.25)
{
int a = int(26 + 25 * std::sin(t));
std::cout << std::string(a, ' ') << (b ? "CREATIVE" : "COMPUTING") << std::endl;
b = !b;
}
return 0;
}
+181
View File
@@ -0,0 +1,181 @@
--
-- Animal.lua
--
-- maintain a flat table/list of all the known animals
local animals = {
"FISH",
"BIRD"
}
-- store the questions as a binary tree with each node having a
-- "y" or "n" branch
-- if the node has a member named "a" it's an answer, with an index
-- into the animals list.
-- Otherwise, it's a question that leads to more nodes.
local questionTree = {
q = "DOES IT SWIM",
y = { a = 1 },
n = { a = 2 }
}
-- print the given prompt string and then wait for input
-- loops until a non-empty input is given
-- returns the input as an upper-case string
function askPrompt(promptString)
local answered = false
local a
while (not answered) do
print(promptString)
a = io.read()
a = string.upper(a)
if (string.len(a) > 0) then
answered = true
end
end
return a
end
-- print the given prompt string and then wait for the
-- user to enter a string beginning with "Y" or "N"
function askYesOrNo(promptString)
local a
while ((a ~= "Y") and (a ~= "N")) do
a = askPrompt(promptString)
a = a:sub(1,1)
end
return a
end
-- prints the introductory text from the original BASIC program
function printIntro()
print(string.format("%32s", " ") .. "ANIMAL")
print(string.format("%15s", " ") .. "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY")
print()
print()
print()
print("PLAY 'GUESS THE ANIMAL'")
print("THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT.")
print()
end
-- prints the animals known in the source list
function listKnownAnimals()
print()
print("ANIMALS I ALREADY KNOW ARE:")
local x
local item
for x = 1,#animals do
-- use string.format to space each animal in a 12-character-wide "cell"
item = string.format("%-12s", animals[x])
-- io.write() works like print(), but doesn't automatically add a carriage return/newline
io.write(item)
-- every fifth item, start a new line
if ((x % 5) == 0) then
io.write("\n")
end
end
print()
print()
end
-- Prompts the user for info about the animal they were thinking of, then
-- uses that to add a new branch to the tree
-- curNode: the node in the tree where the computer made a wrong guess
-- branch: the answer the user gave to curNode's question
function addAnimalToTree(curNode, branch)
local newAnimal
local curResponse = curNode[branch]
local guessedIndex = curResponse.a
local guessedAnimal = animals[guessedIndex]
local newQuestion, newAnswer, newIndex
local newNode
newAnimal = askPrompt("THE ANIMAL YOU WERE THINKING OF WAS A ?")
newQuestion = askPrompt("PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A "..
tostring(newAnimal).." FROM A "..tostring(guessedAnimal))
newAnswer = askYesOrNo("FOR A "..tostring(newAnimal).." THE ANSWER WOULD BE?")
-- add the new animal to the master list at the end, and
-- save off its index in the list
table.insert(animals, newAnimal)
newIndex = #animals
-- create a new node for the question we just learned
newNode = {}
newNode.q = newQuestion
if (newAnswer == "Y") then
newNode.y = { a = newIndex }
newNode.n = { a = guessedIndex }
else
newNode.y = { a = guessedIndex }
newNode.n = { a = newIndex }
end
-- replace the previous answer with our new node
curNode[branch] = newNode
end
-- Starts at the root of the question tree and asks questions about
-- the user's animal until the computer hits an "a" answer node and tries
-- to make a guess
function askAboutAnimal()
local curNode = questionTree
local finished = false
local response, responseIndex
local nextNode, animalName
while (not finished) do
response = askYesOrNo(curNode.q .. "?")
-- convert the response "Y" or "N" to the lowercase "y" or "n" that we use to name our branches
branch = string.lower(response)
nextNode = curNode[branch]
-- is the next node an answer node, or another question?
if (nextNode.a ~= nil) then
-- it's an answer, so make a guess
animalName = animals[nextNode.a]
response = askYesOrNo("IS IT A "..tostring(animalName).."?")
if (response == "Y") then
-- we got the correct answer, so prompt for a new animal
print()
print("WHY NOT TRY ANOTHER ANIMAL?")
else
-- incorrect answer, so add a new entry at this point in the tree
addAnimalToTree(curNode, branch)
end
-- whether we were right or wrong, we're finished with this round
finished = true
else
-- it's another question, so advance down the tree
curNode = nextNode
end
end
end
-- MAIN CONTROL SECTION
printIntro()
-- loop forever until the player requests an exit by entering a blank line
local exitRequested = false
local answer
while (not exitRequested) do
print("ARE YOU THINKING OF AN ANIMAL?")
answer = io.read()
answer = string.upper(answer)
if (string.len(answer) == 0) then
exitRequested = true
elseif (answer:sub(1,4) == "LIST") then
listKnownAnimals()
elseif (answer:sub(1,1) == "Y") then
askAboutAnimal()
end
end
+150
View File
@@ -0,0 +1,150 @@
#!/usr/bin/perl
# Banner program in Perl
# Translated by Kevin Brannen (kbrannen)
use strict;
use warnings;
sub print_lines
{
my $lines = shift;
print "\n" x $lines;
}
# each letter is made of 7 slices (or rows);
# the initial & unused 0 is to allow for Perl arrays being 0-based
# but allow the algorithm to be 1-based (like Basic arrays);
# the numbers are essentially the dots/columns per slice in powers of 2.
my %data = (
" " => [ 0, 0, 0, 0, 0, 0, 0, 0 ],
"." => [ 0, 1, 1, 129, 449, 129, 1, 1 ],
"!" => [ 0, 1, 1, 1, 384, 1, 1, 1 ],
"=" => [ 0, 41, 41, 41, 41, 41, 41, 41 ],
"?" => [ 0, 5, 3, 2, 354, 18, 11, 5 ],
"*" => [ 0, 69, 41, 17, 512, 17, 41, 69 ],
"0" => [ 0, 57, 69, 131, 258, 131, 69, 57 ],
"1" => [ 0, 0, 0, 261, 259, 512, 257, 257 ],
"2" => [ 0, 261, 387, 322, 290, 274, 267, 261 ],
"3" => [ 0, 66, 130, 258, 274, 266, 150, 100 ],
"4" => [ 0, 33, 49, 41, 37, 35, 512, 33 ],
"5" => [ 0, 160, 274, 274, 274, 274, 274, 226 ],
"6" => [ 0, 194, 291, 293, 297, 305, 289, 193 ],
"7" => [ 0, 258, 130, 66, 34, 18, 10, 8 ],
"8" => [ 0, 69, 171, 274, 274, 274, 171, 69 ],
"9" => [ 0, 263, 138, 74, 42, 26, 10, 7 ],
"A" => [ 0, 505, 37, 35, 34, 35, 37, 505 ],
"B" => [ 0, 512, 274, 274, 274, 274, 274, 239 ],
"C" => [ 0, 125, 131, 258, 258, 258, 131, 69 ],
"D" => [ 0, 512, 258, 258, 258, 258, 131, 125 ],
"E" => [ 0, 512, 274, 274, 274, 274, 258, 258 ],
"F" => [ 0, 512, 18, 18, 18, 18, 2, 2 ],
"G" => [ 0, 125, 131, 258, 258, 290, 163, 101 ],
"H" => [ 0, 512, 17, 17, 17, 17, 17, 512 ],
"I" => [ 0, 258, 258, 258, 512, 258, 258, 258 ],
"J" => [ 0, 65, 129, 257, 257, 257, 129, 128 ],
"K" => [ 0, 512, 17, 17, 41, 69, 131, 258 ],
"L" => [ 0, 512, 257, 257, 257, 257, 257, 257 ],
"M" => [ 0, 512, 7, 13, 25, 13, 7, 512 ],
"N" => [ 0, 512, 7, 9, 17, 33, 193, 512 ],
"O" => [ 0, 125, 131, 258, 258, 258, 131, 125 ],
"P" => [ 0, 512, 18, 18, 18, 18, 18, 15 ],
"Q" => [ 0, 125, 131, 258, 258, 322, 131, 381 ],
"R" => [ 0, 512, 18, 18, 50, 82, 146, 271 ],
"S" => [ 0, 69, 139, 274, 274, 274, 163, 69 ],
"T" => [ 0, 2, 2, 2, 512, 2, 2, 2 ],
"U" => [ 0, 128, 129, 257, 257, 257, 129, 128 ],
"V" => [ 0, 64, 65, 129, 257, 129, 65, 64 ],
"W" => [ 0, 256, 257, 129, 65, 129, 257, 256 ],
"X" => [ 0, 388, 69, 41, 17, 41, 69, 388 ],
"Y" => [ 0, 8, 9, 17, 481, 17, 9, 8 ],
"Z" => [ 0, 386, 322, 290, 274, 266, 262, 260 ],
);
my ($horz, $vert, $center, $char, $msg) = (0, 0, '', '', '');
# get args to run with
while ($horz < 1)
{
print "HORIZONTAL (1 or more): ";
chomp($horz = <>);
$horz = int($horz);
}
while ($vert < 1)
{
print "VERTICAL (1 or more): ";
chomp($vert = <>);
$vert = int($vert);
}
print "CENTERED (Y/N): ";
chomp($center = <>);
$center = ($center =~ m/^Y/i) ? 1 : 0;
# note you can enter multiple chars and the program will do the right thing
# thanks to the length() calls below, which was in the original Basic
print "CHARACTER TO PRINT (TYPE 'ALL' IF YOU WANT CHARACTER BEING PRINTED): ";
chomp($char = uc(<>));
while (!$msg)
{
print "STATEMENT: ";
chomp($msg = uc(<>));
}
print "SET PAGE TO PRINT, HIT RETURN WHEN READY";
$_ = <>;
print_lines(2 * $horz);
# print the message
for my $letter ( split(//, $msg) )
{
if (!exists($data{$letter}))
{
die "Cannot use letter '$letter'!";
}
my @s = @{$data{$letter}};
#if ($letter eq " ") { print_lines(7 * $horz); next; }
my $print_letter = ($char eq "ALL") ? $letter : $char;
for my $slice (1 .. 7)
{
my (@j, @f);
for (my $k = 8; $k >= 0; $k--)
{
if (2**$k < $s[$slice])
{
$j[9 - $k] = 1;
$s[$slice] = $s[$slice] - 2**$k;
if ($s[$slice] == 1)
{
$f[$slice] = 9 - $k;
}
}
else
{
$j[9 - $k] = 0;
}
}
for my $t1 (1 .. $horz)
{
print " " x int((63 - 4.5 * $vert) * $center / (length($print_letter)) + 1);
for my $b (1 .. (defined($f[$slice]) ? $f[$slice] : 0))
{
my $str = $j[$b] ? $print_letter : (" " x length($print_letter));
print $str for (1 .. $vert);
}
print "\n";
}
}
# space between letters
print_lines(2 * $horz);
}
# while in the original code, this seems pretty excessive
#print_lines(75);
exit(0);
+18
View File
@@ -1,3 +1,21 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Perl](https://www.perl.org/)
There are two version of the code here, a "faithful" translation (basketball-orig.pl) and
a "modern" translation (basketball.pl). The main difference between the 2 are is that the
faithful translation has 3 GOTOs in it while the modern version has no GOTO. I have added
a "TIME" print when the score is shown so the Clock is visible. Halftime is at "50" and
end of game is at 100 (per the Basic code).
The 3 GOTOs in the faitful version are because of the way the original code jumped into
the "middle of logic" that has no obivious way to avoid ... that I can see, at least while
still maintaining something of the look and structure of the original Basic.
The modern version avoided the GOTOs by restructuring the program in the 2 "play()" subs.
Despite the change, this should play the same way as the faithful version.
All of the percentages remain the same. If writing this from scratch, we really should
have only a single play() sub which uses the same code for both teams, which would also
make the game more fair ... but that wasn't done so the percent edge to Darmouth has been
maintained here.
+415
View File
@@ -0,0 +1,415 @@
#!/usr/bin/perl
# Basketball program in Perl
# This is fairly faithful translation from the original Basic.
# This becomes apparent because there are actually 3 GOTOs still present
# because of the way the original code jumped into the "middle of logic"
# that has no obivious way to avoid ... that I can see.
# For better structure and no GOTOs, see the other version of this program.
# Translated by Kevin Brannen (kbrannen)
use strict;
use warnings;
# globals
my $Defense=0; # dartmouth defense value
my $Opponent; # name of opponent
my @Score = (0, 0); # scores, dart is [0], opponent is [1]
my $Player = 0; # player, 0 = dart, 1 = opp
my $Timer = 0; # time tick, 100 ticks per game, 50 is end of first half, if tie at end then back to T=93
my $DoPlay = 1; # true if game is still being played
my $ConTeam; # controlling team, "dart" or "opp"
my $ShotType = 0; # current shot type
print "\n";
print " " x 31, "BASKETBALL";
print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY";
print "\n\n\n";
print "THIS IS DARTMOUTH COLLEGE BASKETBALL. YOU WILL BE DARTMOUTH\n";
print "CAPTAIN AND PLAYMAKER. CALL SHOTS AS FOLLOWS:\n";
print " 1. LONG (30 FT.) JUMP SHOT;\n";
print " 2. SHORT (15 FT.) JUMP SHOT;\n";
print " 3. LAY UP;\n";
print " 4. SET SHOT.\n";
print "BOTH TEAMS WILL USE THE SAME DEFENSE. CALL DEFENSE AS FOLLOWS:\n";
print " 6. PRESS;\n";
print " 6.5 MAN-TO MAN;\n";
print " 7. ZONE;\n";
print " 7.5 NONE.\n";
print "TO CHANGE DEFENSE, JUST TYPE 0 AS YOUR NEXT SHOT.\n\n";
get_defense();
print "\n";
print "CHOOSE YOUR OPPONENT: ";
chomp($Opponent = <>);
$ConTeam = center_jump();
while ($DoPlay)
{
print "\n";
if ($ConTeam eq "dart")
{
$Player = 0;
get_your_shot();
dartmouth_play();
}
else
{
opponent_play();
}
if ($Timer >= 100)
{
check_end_game();
last if (!$DoPlay);
$Timer = 93;
$ConTeam = center_jump();
}
}
exit(0);
###############################################################
sub dartmouth_play
{
if ($ShotType == 1 || $ShotType == 2)
{
$Timer++;
if ($Timer == 50)
{
end_first_half();
return;
}
if ($Timer == 92)
{
two_min_left();
}
print "JUMP SHOT\n";
if (rand(1) <= 0.341 * $Defense / 8)
{
print "SHOT IS GOOD.\n";
dartmouth_score();
$ConTeam = "opp";
return;
}
if (rand(1) <= 0.682*$Defense/8)
{
print "SHOT IS OFF TARGET.\n";
if ($Defense/6*rand(1) > 0.45)
{
print "REBOUND TO ", $Opponent, "\n";
$ConTeam = "opp";
return;
}
print "DARTMOUTH CONTROLS THE REBOUND.\n";
if (rand(1) <= 0.4) { goto L1300; }
if ($Defense == 6)
{
if (rand(1) <= 0.6)
{
print "PASS STOLEN BY $Opponent, EASY LAYUP.\n";
opp_score();
$ConTeam = "dart"; return;
return;
}
}
print "BALL PASSED BACK TO YOU.\n";
$ConTeam = "dart";
return;
}
if (rand(1) <= 0.782*$Defense/8)
{
print "SHOT IS BLOCKED. BALL CONTROLLED BY "; # no NL
if (rand(1) > 0.5)
{
print "$Opponent.\n";
$ConTeam = "opp";
return;
}
else
{
print "DARTMOUTH.\n";
$ConTeam = "dart";
return;
}
}
if (rand(1) > 0.843*$Defense/8)
{
print "CHARGING FOUL. DARTMOUTH LOSES BALL.\n";
$ConTeam = "opp";
return;
}
else
{
print "SHOOTER IS FOULED. TWO SHOTS.\n";
foul_shooting();
$ConTeam = "opp";
return;
}
}
L1300:
while (1)
{
$Timer++;
if ($Timer == 50)
{
end_first_half();
return;
}
if ($Timer == 92) { two_min_left(); }
if ($ShotType == 0)
{
get_defense();
return;
}
print '', ($ShotType > 3 ? "SET SHOT." : "LAY UP."), "\n";
if (7 / $Defense * rand(1) <= 0.4)
{
print "SHOT IS GOOD. TWO POINTS.\n";
dartmouth_score();
$ConTeam = "opp";
return;
}
if (7 / $Defense * rand(1) <= 0.7)
{
print "SHOT IS OFF THE RIM.\n";
if (rand(1) <= 0.667)
{
print "$Opponent CONTROLS THE REBOUND.\n";
$ConTeam = "opp";
return;
}
print "DARTMOUTH CONTROLS THE REBOUND.\n";
next if (rand(1) <= 0.4);
print "BALL PASSED BACK TO YOU.\n";
$ConTeam = "dart";
return;
}
if (7 / $Defense * rand(1) <= 0.875)
{
print "SHOOTER FOULED. TWO SHOTS.\n";
foul_shooting();
$ConTeam = "opp";
return;
}
if (7 / $Defense * rand(1) <= 0.925)
{
print "SHOT BLOCKED. $Opponent\'S BALL.\n";
$ConTeam = "opp";
return;
}
print "CHARGING FOUL. DARTMOUTH LOSES THE BALL.\n";
$ConTeam = "opp";
return;
}
}
sub get_defense
{
$Defense = 0;
while ($Defense < 6 || $Defense > 7.5)
{
print "YOUR NEW DEFENSIVE ALLIGNMENT IS (6, 6.5, 7. 7.5): ";
chomp($Defense = <>);
($Defense) =~ m/(\d(\.\d)?)/;
}
}
sub opponent_play
{
$Player = 1;
$Timer++;
if ($Timer == 50)
{
end_first_half();
$ConTeam = center_jump();
return;
}
print "\n";
while (1)
{
my $shot = 10.0 / 4 * rand(1) + 1;
if ($shot <= 2.0)
{
print "JUMP SHOT.\n";
if (8.0 / $Defense * rand(1) <= 0.35)
{
print "SHOT IS GOOD.\n";
opp_score();
$ConTeam = "dart";
return;
}
if (8.0 / $Defense * rand(1) <= 0.75)
{
print "SHOT IS OFF RIM.\n";
L3110:
if ($Defense / 6.0 * rand(1) <= 0.5)
{
print "DARTMOUTH CONTROLS THE REBOUND.\n";
$ConTeam = "dart";
return;
}
print "$Opponent CONTROLS THE REBOUND.\n";
if ($Defense == 6)
{
if (rand(1) <= 0.75)
{
print "BALL STOLEN. EASY LAY UP FOR DARTMOUTH.\n";
dartmouth_score();
$ConTeam = "opp";
return;
}
}
if (rand(1) <= 0.5)
{
print "PASS BACK TO $Opponent GUARD.\n";
$ConTeam = "opp";
return;
}
goto L3500;
}
if (8.0 / $Defense * rand(1) <= 0.9)
{
print "PLAYER FOULED. TWO SHOTS.\n";
foul_shooting();
$ConTeam = "dart";
return;
}
print "OFFENSIVE FOUL. DARTMOUTH'S BALL.\n";
$ConTeam = "dart";
return;
}
L3500:
print ($shot > 3 ? "SET SHOT.\n" : "LAY UP.\n");
if (7.0 / $Defense * rand(1) > 0.413)
{
print "SHOT IS MISSED.\n";
{
no warnings;
goto L3110;
}
}
else
{
print "SHOT IS GOOD.\n";
opp_score();
$ConTeam = "dart";
return;
}
}
}
sub opp_score
{
$Score[0] += 2;
print_score();
}
sub dartmouth_score
{
$Score[1] += 2;
print_score();
}
sub print_score
{
print "SCORE: $Score[1] TO $Score[0]\n";
print "TIME: $Timer\n";
}
sub end_first_half
{
print "\n ***** END OF FIRST HALF *****\n\n";
print "SCORE: DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n\n";
center_jump();
}
sub get_your_shot
{
$ShotType = -1;
while ($ShotType < 0 || $ShotType > 4)
{
print "YOUR SHOT (0-4): ";
chomp($ShotType = <>);
$ShotType = int($ShotType);
if ($ShotType < 0 || $ShotType > 4)
{
print "INCORRECT ANSWER. RETYPE IT. ";
}
}
}
sub center_jump
{
print "CENTER JUMP\n";
if (rand(1) <= 0.6)
{
print "$Opponent CONTROLS THE TAP.\n";
return "opp";
}
print "DARTMOUTH CONTROLS THE TAP.\n";
return "dart";
}
sub check_end_game
{
print "\n";
if ($Score[1] != $Score[0])
{
print " ***** END OF GAME *****\n";
print "FINAL SCORE: DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n";
$DoPlay = 0;
}
else
{
print "\n ***** END OF SECOND HALF *****\n";
print "SCORE AT END OF REGULATION TIME:\n";
print " DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n";
print "BEGIN TWO MINUTE OVERTIME PERIOD\n";
}
}
sub two_min_left
{
print "\n *** TWO MINUTES LEFT IN THE GAME ***\n\n";
}
sub foul_shooting
{
if (rand(1) > 0.49)
{
if (rand(1) > 0.75)
{
print "BOTH SHOTS MISSED.\n";
}
else
{
print "SHOOTER MAKES ONE SHOT AND MISSES ONE.\n";
$Score[1 - $Player]++;
}
}
else
{
print "SHOOTER MAKES BOTH SHOTS.\n";
$Score[1 - $Player] += 2;
}
print_score();
}
+393
View File
@@ -0,0 +1,393 @@
#!/usr/bin/perl
# Basketball program in Perl
# While this should play the same way as the fairly faithful translation version,
# there are no GOTOs in this code. That was achieved by restructuring the program
# in the 2 *_play() subs. All of the percentages remain the same. If writing this
# from scratch, we really should have only a play() sub which uses the same code
# for both teams, but the percent edge to Darmouth has been maintained here.
# Translated by Kevin Brannen (kbrannen)
use strict;
use warnings;
# globals
my $Defense=0; # dartmouth defense value
my $Opponent; # name of opponent
my @Score = (0, 0); # scores, dart is [0], opponent is [1]
my $Player = 0; # player, 0 = dart, 1 = opp
my $Timer = 0; # time tick, 100 ticks per game, 50 is end of first half, if tie at end then back to T=93
my $DoPlay = 1; # true if game is still being played
my $ConTeam; # controlling team, "dart" or "opp"
my $ShotType = 0; # current shot type
print "\n";
print " " x 31, "BASKETBALL";
print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY";
print "\n\n\n";
print "THIS IS DARTMOUTH COLLEGE BASKETBALL. YOU WILL BE DARTMOUTH\n";
print "CAPTAIN AND PLAYMAKER. CALL SHOTS AS FOLLOWS:\n";
print " 1. LONG (30 FT.) JUMP SHOT;\n";
print " 2. SHORT (15 FT.) JUMP SHOT;\n";
print " 3. LAY UP;\n";
print " 4. SET SHOT.\n";
print "BOTH TEAMS WILL USE THE SAME DEFENSE. CALL DEFENSE AS FOLLOWS:\n";
print " 6. PRESS;\n";
print " 6.5 MAN-TO MAN;\n";
print " 7. ZONE;\n";
print " 7.5 NONE.\n";
print "TO CHANGE DEFENSE, JUST TYPE 0 AS YOUR NEXT SHOT.\n\n";
get_defense();
print "\n";
print "CHOOSE YOUR OPPONENT: ";
chomp($Opponent = <>);
$ConTeam = center_jump();
while ($DoPlay)
{
print "\n";
if ($ConTeam eq "dart")
{
get_your_shot();
dartmouth_play();
}
else
{
$ShotType = 10.0 / 4 * rand(1) + 1;
opponent_play();
}
if ($Timer >= 100)
{
check_end_game();
last if (!$DoPlay);
$Timer = 93;
$ConTeam = center_jump();
}
}
exit(0);
###############################################################
sub dartmouth_play
{
$Player = 0;
print "\n";
while (1)
{
$Timer++;
if ($Timer == 50) { end_first_half(); return; }
if ($Timer == 92) { two_min_left(); }
if ($ShotType == 0)
{
get_defense();
return; # for new ShotType
}
elsif ($ShotType == 1 || $ShotType == 2)
{
print "JUMP SHOT\n";
if (rand(1) <= 0.341 * $Defense / 8)
{
print "SHOT IS GOOD.\n";
dartmouth_score();
last;
}
if (rand(1) <= 0.682*$Defense/8)
{
print "SHOT IS OFF TARGET.\n";
if ($Defense/6*rand(1) > 0.45)
{
print "REBOUND TO $Opponent\n";
last;
}
print "DARTMOUTH CONTROLS THE REBOUND.\n";
if (rand(1) <= 0.4)
{
$ShotType = (rand(1) <= 0.5) ? 3 : 4;
next;
}
if ($Defense == 6)
{
if (rand(1) <= 0.6)
{
print "PASS STOLEN BY $Opponent, EASY LAYUP.\n";
opp_score();
next;
}
}
print "BALL PASSED BACK TO YOU.\n";
next;
}
if (rand(1) <= 0.782*$Defense/8)
{
print "SHOT IS BLOCKED. BALL CONTROLLED BY "; # no NL
if (rand(1) > 0.5)
{
print "$Opponent.\n";
last;
}
else
{
print "DARTMOUTH.\n";
next;
}
}
if (rand(1) > 0.843*$Defense/8)
{
print "CHARGING FOUL. DARTMOUTH LOSES BALL.\n";
last;
}
else
{
print "SHOOTER IS FOULED. TWO SHOTS.\n";
foul_shooting();
last;
}
}
else # elsif ($ShotType >= 3)
{
print '', ($ShotType > 3 ? "SET SHOT." : "LAY UP."), "\n";
if (7 / $Defense * rand(1) <= 0.4)
{
print "SHOT IS GOOD. TWO POINTS.\n";
dartmouth_score();
last;
}
if (7 / $Defense * rand(1) <= 0.7)
{
print "SHOT IS OFF THE RIM.\n";
if (rand(1) <= 0.667)
{
print "$Opponent CONTROLS THE REBOUND.\n";
last;
}
print "DARTMOUTH CONTROLS THE REBOUND.\n";
next if (rand(1) <= 0.4);
print "BALL PASSED BACK TO YOU.\n";
next;
}
if (7 / $Defense * rand(1) <= 0.875)
{
print "SHOOTER FOULED. TWO SHOTS.\n";
foul_shooting();
last;
}
if (7 / $Defense * rand(1) <= 0.925)
{
print "SHOT BLOCKED. $Opponent\'S BALL.\n";
last;
}
print "CHARGING FOUL. DARTMOUTH LOSES THE BALL.\n";
last;
}
}
$ConTeam = "opp";
}
sub get_defense
{
$Defense = 0;
do {
print "YOUR NEW DEFENSIVE ALLIGNMENT IS (6, 6.5, 7. 7.5): ";
chomp($Defense = <>);
($Defense) =~ m/(\d(\.\d)?)/;
} while ($Defense < 6.0 || $Defense > 7.5)
}
sub opponent_play
{
$Player = 1;
print "\n";
while (1)
{
$Timer++;
if ($Timer == 50) { end_first_half(); return; }
if ($Timer == 92) { two_min_left(); }
if ($ShotType <= 2.0)
{
print "JUMP SHOT.\n";
if (8.0 / $Defense * rand(1) <= 0.35)
{
print "SHOT IS GOOD.\n";
opp_score();
last;
}
if (8.0 / $Defense * rand(1) <= 0.75)
{
print "SHOT IS OFF RIM.\n";
opp_missed();
return; # for possible new ShotType or team change
}
if (8.0 / $Defense * rand(1) <= 0.9)
{
print "PLAYER FOULED. TWO SHOTS.\n";
foul_shooting();
last;
}
print "OFFENSIVE FOUL. DARTMOUTH'S BALL.\n";
last;
}
else # ShotType >= 3
{
print ($ShotType > 3 ? "SET SHOT.\n" : "LAY UP.\n");
if (7.0 / $Defense * rand(1) > 0.413)
{
print "SHOT IS MISSED.\n";
{
opp_missed();
return; # for possible new ShotType or team change
}
}
else
{
print "SHOT IS GOOD.\n";
opp_score();
last;
}
}
}
$ConTeam = "dart";
}
sub opp_missed
{
if ($Defense / 6.0 * rand(1) <= 0.5)
{
print "DARTMOUTH CONTROLS THE REBOUND.\n";
$ConTeam = "dart";
}
else
{
print "$Opponent CONTROLS THE REBOUND.\n";
if ($Defense == 6)
{
if (rand(1) <= 0.75)
{
print "BALL STOLEN. EASY LAY UP FOR DARTMOUTH.\n";
dartmouth_score();
#$ConTeam = "opp";
return; # for possible new ShotType
}
}
if (rand(1) <= 0.5)
{
print "PASS BACK TO $Opponent GUARD.\n";
#$ConTeam = "opp";
return; # for possible new ShotType
}
$ShotType = (rand(1) <= 0.5) ? 3 : 4;
}
}
sub opp_score
{
$Score[0] += 2;
print_score();
}
sub dartmouth_score
{
$Score[1] += 2;
print_score();
}
sub print_score
{
print "SCORE: $Score[1] TO $Score[0]\n";
print "TIME: $Timer\n";
}
sub end_first_half
{
print "\n ***** END OF FIRST HALF *****\n\n";
print "SCORE: DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n\n";
$ConTeam = center_jump();
}
sub get_your_shot
{
$ShotType = -1;
while ($ShotType < 0 || $ShotType > 4)
{
print "YOUR SHOT (0-4): ";
chomp($ShotType = <>);
$ShotType = int($ShotType);
if ($ShotType < 0 || $ShotType > 4)
{
print "INCORRECT ANSWER. RETYPE IT. ";
}
}
}
sub center_jump
{
print "CENTER JUMP\n";
if (rand(1) <= 0.6)
{
print "$Opponent CONTROLS THE TAP.\n";
return "opp";
}
print "DARTMOUTH CONTROLS THE TAP.\n";
return "dart";
}
sub check_end_game
{
print "\n";
if ($Score[1] != $Score[0])
{
print " ***** END OF GAME *****\n";
print "FINAL SCORE: DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n";
$DoPlay = 0;
}
else
{
print "\n ***** END OF SECOND HALF *****\n";
print "SCORE AT END OF REGULATION TIME:\n";
print " DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n";
print "BEGIN TWO MINUTE OVERTIME PERIOD\n";
}
}
sub two_min_left
{
print "\n *** TWO MINUTES LEFT IN THE GAME ***\n\n";
}
sub foul_shooting
{
if (rand(1) > 0.49)
{
if (rand(1) > 0.75)
{
print "BOTH SHOTS MISSED.\n";
}
else
{
print "SHOOTER MAKES ONE SHOT AND MISSES ONE.\n";
$Score[1 - $Player]++;
}
}
else
{
print "SHOOTER MAKES BOTH SHOTS.\n";
$Score[1 - $Player] += 2;
}
print_score();
}
+3
View File
@@ -1,3 +1,6 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Perl](https://www.perl.org/)
Added feature so that if "TIME" value is "0" then it will quit,
so you don't have to hit Control-C. Also added a little error checking of the input.
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/perl
# Bounce program in Perl
# Translated by Kevin Brannen (kbrannen)
use strict;
use warnings;
print "\n";
print " " x 31,"BOUNCE\n";
print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n";
print "\n\n\n";
print "THIS SIMULATION LETS YOU SPECIFY THE INITIAL VELOCITY\n";
print "OF A BALL THROWN STRAIGHT UP, AND THE COEFFICIENT OF\n";
print "ELASTICITY OF THE BALL. PLEASE USE A DECIMAL FRACTION\n";
print "COEFFICIENCY (LESS THAN 1).\n\n";
print "YOU ALSO SPECIFY THE TIME INCREMENT TO BE USED IN\n";
print "'STROBING' THE BALL'S FLIGHT (TRY .1 INITIALLY).\n\n";
# deal with basic's tab() for line positioning
# line = line string we're starting with
# pos = position to start writing
# s = string to write
# returns the resultant string, which might not have been changed
sub line_tab
{
my ($line, $pos, $s) = @_;
my $len = length($line);
# if curser is past position, do nothing
if ($len <= $pos) { $line .= " " x ($pos - $len) . $s; }
return $line;
}
while (1)
{
my @T; # time slice?
my $time_inc; # time increment, probably in fractions of seconds
my $velocity; # velocity in feet/sec
my $coeff_elas; # coeeficent of elasticity
my $line_pos; # position on line
my $S1 # duration in full seconds?
# get input
print "TIME INCREMENT (SEC, 0=QUIT): "; chomp($time_inc = <>);
last if ($time_inc == 0);
print "VELOCITY (FPS): "; chomp($velocity = <>);
print "COEFFICIENT: "; chomp($coeff_elas = <>);
if ($coeff_elas >= 1.0 || $coeff_elas <= 0)
{
print "COEFFICIENT MUST BE > 0 AND < 1.0\n\n\n";
next;
}
print "\nFEET\n";
$S1 = int(70.0 / ($velocity / (16.0 * $time_inc)));
for my $i (1 .. $S1)
{
$T[$i] = $velocity * $coeff_elas ** ($i - 1) / 16.0;
}
# draw graph
for (my $height=int(-16.0 * ($velocity / 32.0) ** 2.0 + $velocity ** 2.0 / 32.0 + .5) ; $height >= 0 ; $height -= .5)
{
if (int($height) == $height) { print sprintf("%2d", $height); }
else { print " "; }
$line_pos = 0;
my $curr_line = "";
for my $i (1 .. $S1)
{
my $time;
for ($time=0 ; $time <= $T[$i] ; $time += $time_inc)
{
$line_pos += $time_inc;
if (abs($height - (.5 * (-32) * $time ** 2.0 + $velocity * $coeff_elas ** ($i - 1) * $time)) <= .25)
{
$curr_line = line_tab($curr_line, ($line_pos / $time_inc), "0");
}
}
$time = ($T[$i + 1] // 0) / 2; # we can reach 1 past the end, use 0 if that happens
last if (-16.0 * $time ** 2.0 + $velocity * $coeff_elas ** ($i - 1) * $time < $height);
}
print "$curr_line\n";
}
# draw scale
print " .";
print "." x (int($line_pos + 1) / $time_inc + 1), "\n";
print " 0";
my $curr_line = "";
for my $i (1 .. int($line_pos + .9995))
{
$curr_line = line_tab($curr_line, int($i / $time_inc), $i);
}
print "$curr_line\n";
print " " x (int($line_pos + 1) / (2 * $time_inc) - 2), "SECONDS\n\n";
}
+19
View File
@@ -1,3 +1,22 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Perl](https://www.perl.org/)
###Bowling program in Perl
Run normally, this is a fairly faithful translation of the Basic game.
The only real differences are a few trivial fix-ups on the prints to make it
look better, and the player/frame/ball line was put before the "get the ball
going" line to make it more obvious who's turn it is.
However, if you run it with "-a" on the command line, it will go into
"advanced" mode, which means that "." is used to show pin down and "!" for
pin up, current running scores are shown at the end of each frame, and the
scoring also looks more normal at the end. This is all done because I think it
looks better and I wanted to see a score. Having a flag says you can play
whichever version of the game you like.
Note, the original code doesn't do the 10th frame correctly, in that it will
never do more than 2 balls, so the best score you can get is a 290.
This is true in both modes. That being said, it will always give you a mediocre
game; I don't think I've ever seen a score over 140.
+254
View File
@@ -0,0 +1,254 @@
#!/usr/bin/perl
# Bowling program in Perl
# Run normally, this is a fairly faithful translation of the Basic game.
# The only real differences are a few trivial fix-ups on the prints to make it
# look better, and the player/frame/ball line was put before the "get the ball
# going" line to make it more obvious who's turn it is.
#
# However, if you run it with "-a" on the command line, it will go into
# 'advanced' mode, which means that "." is used to show pin down and "!" for
# pin up, current running scores are shown at the end of each frame, and the
# scoring also looks more normal at the end. This is all done because I think it
# looks better and I wanted to see a score. Having a flag says you can play
# whichever version of the game you like.
#
# Note, the original code doesn't do the 10th frame correctly, in that it will
# never do more than 2 balls, so the best score you can get is a 290.
# This is true in both modes. That being said, it will always give you a mediocre game.
# Translated by Kevin Brannen (kbrannen)
use strict;
use warnings;
print "\n";
print " " x 34, "BASKETBALL\n";
print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n";
# globals
my @C; # pin position matraix?
my @Scores; # scores: [player num][frame][ball] # ball3 is end-result, ball4 is frame score (advanced mode)
my $Answer; # get answers
my $Num_players; # number of players
my $Advanced = 0; # flag, 1 to use advanced code, 0 for original code
my $char_down = '0'; # char to show pin is down
my $char_up = '+'; # char to show pin is standing
if ($ARGV[0] && $ARGV[0] eq "-a")
{
shift;
$Advanced = 1;
$char_down = '.';
$char_up = '!';
}
print "WELCOME TO THE ALLEY\n";
print "BRING YOUR FRIENDS\n";
print "OKAY LET'S FIRST GET ACQUAINTED\n\n";
print "SEE THE INSTRUCTIONS (Y/N): ";
chomp($Answer = uc(<>));
if ($Answer eq "Y")
{
print "THE GAME OF BOWLING TAKES MIND AND SKILL. DURING THE GAME\n";
print "THE COMPUTER WILL KEEP SCORE.YOU MAY COMPETE WITH\n";
print "OTHER PLAYERS[UP TO FOUR]. YOU WILL BE PLAYING TEN FRAMES\n";
print "ON THE PIN DIAGRAM 'O' MEANS THE PIN IS DOWN...'+' MEANS THE\n";
print "PIN IS STANDING. AFTER THE GAME THE COMPUTER WILL SHOW YOUR SCORES.\n";
}
do {
print "FIRST OF ALL...HOW MANY ARE PLAYING (1-4): ";
$Num_players = int(<>);
} while ($Num_players < 1 || $Num_players > 4);
print "\nVERY GOOD...\n";
while (1)
{
# reset all scores
for my $p (1 .. $Num_players) # players
{
for my $f (1 .. 10) # frames
{
for my $b (1 .. 3) # balls
{
$Scores[$p][$f][$b] = 0;
}
}
}
# play the game
for my $frame (1 .. 10) # frame
{
for my $curr_player (1 .. $Num_players) # player
{
my $last_pins=0; # pins down for last ball
my $ball=1; # ball number, 1 or 2
my $end_result=0; # result at end of turn: 3=strike, 2=spare, 1=pins-left
for my $i (1 .. 15) { $C[$i] = 0 }
while (1) # another ball
{
# ARK BALL GENERATOR USING MOD '15' SYSTEM
my $K=0;
my $curr_pins=0; # pins down for this ball
for my $i (1 .. 20)
{
my $x = int(rand(1) * 100);
my $j;
for ($j=1 ; $j <= 10 ; $j++)
{
last if ($x < 15 * $j);
}
$C[15 * $j - $x] = 1;
}
# ARK PIN DIAGRAM
print "PLAYER: $curr_player FRAME: $frame BALL: $ball\n";
print "PRESS ENTER TO GET THE BALL GOING.";
$Answer = <>; # not used, just need an enter
for my $i (0 .. 3)
{
print "\n";
print " " x $i; # avoid the TAB(), just shift each row over for the triangle
for my $j (1 .. 4 - $i)
{
$K++;
print ($C[$K] == 1 ? " $char_down" : " $char_up");
}
}
print "\n";
# ARK ROLL ANALYSIS
for my $i (1 .. 10)
{
$curr_pins += $C[$i];
}
if ($curr_pins - $last_pins == 0)
{
print "GUTTER!!\n";
}
if ($ball == 1 && $curr_pins == 10)
{
print "STRIKE!!!!!\a\a\a\a\n"; # \a is for bell
$end_result = 3;
}
elsif ($ball == 2 && $curr_pins == 10)
{
print "SPARE!!!!\n";
$end_result = 2;
}
elsif ($ball == 2 && $curr_pins < 10)
{
if ($Advanced) { print 10 - $curr_pins, " PENS LEFT!!!\n"; }
else { print "ERROR!!!\n"; }
$end_result = 1;
}
if ($ball == 1 && $curr_pins < 10)
{
print "ROLL YOUR 2ND BALL\n";
}
print "\n";
# ARK STORAGE OF THE SCORES
if ($Advanced) { $Scores[$curr_player][$frame][$ball] = $curr_pins - $last_pins; }
else { $Scores[$curr_player][$frame][$ball] = $curr_pins; }
if ($ball == 1)
{
$ball = 2;
$last_pins = $curr_pins;
if ($end_result == 3) # strike, no more rolls, goto last
{
$Scores[$curr_player][$frame][$ball] = $curr_pins;
}
else
{
$Scores[$curr_player][$frame][$ball] = $curr_pins - $last_pins;
next if ($end_result == 0); # next roll
}
}
last;
}
$Scores[$curr_player][$frame][3] = $end_result;
} # next player
if ($Advanced)
{
print "Scores:\n";
for my $p (1 .. $Num_players)
{
my $total = calc_score($p);
print "\tPlayer $p: $total\n";
}
print "\n";
}
} # next frame
# end of game, show full scoreboard
show_scoreboard();
print "DO YOU WANT ANOTHER GAME (Y/N): ";
chomp($Answer = uc(<>));
print "\n";
last if ($Answer ne "Y");
}
exit(0);
sub show_scoreboard
{
print "FRAMES\n";
for my $i (1 .. 10)
{
print " $i ";
}
print "\n";
my @results = ( "-", ".", "/", "X" );
for my $p (1 .. $Num_players)
{
print "Player $p\n" if ($Advanced);
my $ball_max = ($Advanced ? 4 : 3);
for my $b (1 .. $ball_max)
{
for my $f (1 .. 10)
{
if ($b != 3) { print sprintf("%2d ", $Scores[$p][$f][$b]); }
else { print sprintf("%2s ", $results[$Scores[$p][$f][$b]]); }
}
print "\n";
}
print "\n";
}
}
sub calc_score
{
my $player = shift;
my $total = 0;
for my $frame (1 .. 10)
{
my $score = 0;
if ($frame == 10 || $Scores[$player][$frame][3] == 1) # pins
{
$score = $Scores[$player][$frame][1] + $Scores[$player][$frame][2];
}
elsif ($Scores[$player][$frame][3] == 2) # spare
{
$score = 10 + $Scores[$player][$frame+1][1];
}
elsif ($Scores[$player][$frame][3] == 3) # strike
{
$score = 10 + $Scores[$player][$frame+1][1];
if ($Scores[$player][$frame+1][1] == 10)
{
$score += ($frame < 9 ? $Scores[$player][$frame+2][1] : $Scores[$player][$frame+1][2]);
}
else
{
$score += $Scores[$player][$frame+1][2];
}
}
$Scores[$player][$frame][4] = $score;
$total += $score;
}
return $total;
}
+252
View File
@@ -0,0 +1,252 @@
#!/usr/bin/perl
# Boxing program in Perl
# Required extensive restructuring to remove all of the GOTO's.
# Translated by Kevin Brannen (kbrannen)
use strict;
use warnings;
# globals
my $Opp_won = 0; # num rounds opponent has won
my $You_won = 0; # num rounds you have won
my $Opp_name = ""; # opponent name
my $Your_name = ""; # your name
my $Your_best = 0; # your best punch
my $Your_worst = 0; # your worst punch
my $Opp_best; # opponent best punch
my $Opp_worst; # opponent worst punch
my $Opp_damage; # opponent damage ?
my $Your_damage; # your damage ?
sub get_punch
{
my $prompt = shift;
my $p;
while (1)
{
print "$prompt: ";
chomp($p = int(<>));
last if ($p >= 1 && $p <= 4);
print "DIFFERENT PUNCHES ARE: (1) FULL SWING; (2) HOOK; (3) UPPERCUT; (4) JAB.\n";
}
return $p;
}
print "\n";
print " " x 33, "BOXING\n";
print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n";
print "BOXING OLYMPIC STYLE (3 ROUNDS -- 2 OUT OF 3 WINS)\n\n";
print "WHAT IS YOUR OPPONENT'S NAME: ";
chomp($Opp_name = <>);
print "INPUT YOUR MAN'S NAME: ";
chomp($Your_name = <>);
print "DIFFERENT PUNCHES ARE: (1) FULL SWING; (2) HOOK; (3) UPPERCUT; (4) JAB.\n";
$Your_best = get_punch("WHAT IS YOUR MANS BEST");
$Your_worst = get_punch("WHAT IS HIS VULNERABILITY");
do {
$Opp_best = int(4*rand(1)+1);
$Opp_worst = int(4*rand(1)+1);
} while ($Opp_best == $Opp_worst);
print "$Opp_name\'S ADVANTAGE IS $Opp_best AND VULNERABILITY IS SECRET.\n\n";
for my $R (1 .. 3) # rounds
{
last if ($Opp_won >= 2 || $You_won >= 2);
$Opp_damage = 0;
$Your_damage = 0;
print "ROUND $R BEGINS...\n";
for my $R1 (1 .. 7) # 7 events per round?
{
if (int(10*rand(1)+1) <= 5)
{
my $your_punch = get_punch("$Your_name\'S PUNCH");
$Opp_damage += 2 if ($your_punch == $Your_best);
if ($your_punch == 1) { punch1(); }
elsif ($your_punch == 2) { punch2(); }
elsif ($your_punch == 3) { punch3(); }
else { punch4(); }
next;
}
my $Opp_punch = int(4*rand(1)+1);
$Your_damage += 2 if ($Opp_punch == $Opp_best);
if ($Opp_punch == 1) { opp1(); }
elsif ($Opp_punch == 2) { opp2(); }
elsif ($Opp_punch == 3) { opp3(); }
else { opp4(); }
}
if ($Opp_damage > $Your_damage)
{
print "\n$Your_name WINS ROUND $R\n\n";
$You_won++;
}
else
{
print "\n$Opp_name WINS ROUND $R\n\n";
$Opp_won++;
}
}
if ($Opp_won >= 2)
{
done("$Opp_name WINS (NICE GOING, $Opp_name).");
}
#else # if ($You_won >= 2)
done("$Your_name AMAZINGLY WINS!!");
###################################################
sub done
{
my $msg = shift;
print $msg;
print "\n\nAND NOW GOODBYE FROM THE OLYMPIC ARENA.\n\n";
exit(0);
}
sub punch1
{
# $your_punch == 1, full swing
print "$Your_name SWINGS AND ";
if ($Opp_worst == 4 || int(30*rand(1)+1) < 10)
{
print "HE CONNECTS!\n";
if ($Opp_damage > 35)
{
done("$Opp_name IS KNOCKED COLD AND $Your_name IS THE WINNER AND CHAMP! ");
}
$Opp_damage += 15;
}
else
{
print "HE MISSES\n";
print "\n\n" if ($Opp_damage != 1);
}
}
sub punch2
{
# $your_punch == 2, hook
print "$Your_name GIVES THE HOOK... ";
if ($Opp_worst == 2)
{
$Opp_damage += 7;
return;
}
if (int(2*rand(1)+1) == 1)
{
print "BUT IT'S BLOCKED!!!!!!!!!!!!!\n";
}
else
{
print "CONNECTS...\n";
$Opp_damage += 7;
}
}
sub punch3
{
# $your_punch == 3, uppercut
print "$Your_name TRIES AN UPPERCUT ";
if ($Opp_worst == 3 || int(100*rand(1)+1) < 51)
{
print "AND HE CONNECTS!\n";
$Opp_damage += 4;
}
else
{
print "AND IT'S BLOCKED (LUCKY BLOCK!)\n";
}
}
sub punch4
{
# $your_punch == 4, jab
print "$Your_name JABS AT $Opp_name\'S HEAD ";
if ($Opp_worst == 4 || (int(8*rand(1)+1)) >= 4)
{
$Opp_damage += 3;
print "\n";
}
else
{
print "IT'S BLOCKED.\n";
}
}
sub opp1
{
# opp_punch == 1
print "$Opp_name TAKES A FULL SWING AND ";
if ($Your_worst == 1 || int(60*rand(1)+1) < 30)
{
print " POW!!!!! HE HITS HIM RIGHT IN THE FACE!\n";
if ($Your_damage > 35)
{
done("$Your_name IS KNOCKED COLD AND $Opp_name IS THE WINNER AND CHAMP!");
}
$Your_damage += 15;
}
else
{
print " IT'S BLOCKED!\n";
}
}
sub opp2
{
# opp_punch == 2
print "$Opp_name GETS $Your_name IN THE JAW (OUCH!)\n";
$Your_damage += 7;
print "....AND AGAIN!\n";
$Your_damage += 5;
if ($Your_damage > 35)
{
done("$Your_name IS KNOCKED COLD AND $Opp_name IS THE WINNER AND CHAMP!");
}
print "\n";
# 2 continues into opp_punch == 3
opp3();
}
sub opp3()
{
# opp_punch == 3
print "$Your_name IS ATTACKED BY AN UPPERCUT (OH,OH)...\n";
if ($Your_worst != 3 && int(200*rand(1)+1) > 75)
{
print " BLOCKS AND HITS $Opp_name WITH A HOOK.\n";
$Opp_damage += 5;
}
else
{
print "AND $Opp_name CONNECTS...\n";
$Your_damage += 8;
}
}
sub opp4
{
# opp_punch == 4
print "$Opp_name JABS AND ";
if ($Your_worst == 4)
{
$Your_damage += 5;
}
elsif (int(7*rand(1)+1) > 4)
{
print " BLOOD SPILLS !!!\n";
$Your_damage += 5;
}
else
{
print "IT'S BLOCKED!\n";
}
}
+6
View File
@@ -1,3 +1,9 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Perl](https://www.perl.org/)
Note: This version has lines and columns numbers to help you with choosing the cell
to move from and to, so you don't have to continually count. It also puts a "." only for
blank cells you can move to, which I think makes for a more pleasing look and makes
it easier to play. If you want the original behavior, start the program with an arg
of "-o" for the original behavior.
+351
View File
@@ -0,0 +1,351 @@
#!/usr/bin/perl
# Checkers program in Perl
# Started with checkers.annotated.bas
# Translated by Kevin Brannen (kbrannen)
use strict;
use warnings;
# globals
#
# The current move: (rating, current x, current y, new x, new y)
# 'rating' represents how good the move is; higher is better.
my @ratings = (-99); # (4); # Start with minimum score
# The board. Pieces are represented by numeric values:
#
# - 0 = empty square
# - -1,-2 = X (-1 for regular piece, -2 for king)
# - 1,2 = O (1 for regular piece, 2 for king)
#
# This program's player ("me") plays X.
my @board; # (7,7)
# chars to print for the board, add 2 to the board value as an index to the char
my @chars = ("X*", "X", ".", "O", "O*");
my $neg1 = -1; # constant holding -1
my $winner = "";
my $upgrade = shift(@ARGV) // "";
$upgrade = $upgrade eq "-o" ? 0 : 1;
#####
print "\n";
print " " x 32, "CHECKERS\n";
print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n";
print "THIS IS THE GAME OF CHECKERS. THE COMPUTER IS X,\n";
print "AND YOU ARE O. THE COMPUTER WILL MOVE FIRST.\n";
print "SQUARES ARE REFERRED TO BY A COORDINATE SYSTEM.\n";
print "(0,0) IS THE LOWER LEFT CORNER\n";
print "(0,7) IS THE UPPER LEFT CORNER\n";
print "(7,0) IS THE LOWER RIGHT CORNER\n";
print "(7,7) IS THE UPPER RIGHT CORNER\n";
print "THE COMPUTER WILL TYPE '+TO' WHEN YOU HAVE ANOTHER\n";
print "JUMP. TYPE TWO NEGATIVE NUMBERS IF YOU CANNOT JUMP.\n";
print "ENTER YOUR MOVE POSITION LIKE '0 0' OR '0,0'.\n\n\n";
# Initialize the board. Data is 2 length-wise strips repeated.
my @data = ();
for (1 .. 32) { push(@data, (1,0,1,0,0,0,-1,0, 0,1,0,0,0,-1,0,-1)); }
for my $x (0 .. 7)
{
for my $y (0 .. 7)
{
$board[$x][$y] = shift(@data);
}
}
# Start of game loop. First, my turn.
while (1)
{
# For each square on the board, search for one of my pieces
# and if it can make the best move so far, store that move in 'r'
for my $x (0 .. 7)
{
for my $y (0 .. 7)
{
# Skip if this is empty or an opponent's piece
next if ($board[$x][$y] > -1);
# If this is one of my ordinary pieces, analyze possible
# forward moves.
if ($board[$x][$y] == -1)
{
for (my $a = -1 ; $a <= 1 ; $a +=2)
{
$b = $neg1;
find_move($x, $y, $a, $b);
}
}
# If this is one of my kings, analyze possible forward
# and backward moves.
if ($board[$x][$y] == -2)
{
for (my $a = -1 ; $a <= 1 ; $a += 2)
{
for (my $b = -1 ; $a <= 1 ; $b += 2) { find_move($x, $y, $a, $b); }
}
}
}
}
if ($ratings[0] == -99) # Game is lost if no move could be found.
{
$winner = "you";
last;
}
# Print the computer's move. (Note: chr$(30) is an ASCII RS
# (record separator) code; probably no longer relevant.)
print "FROM $ratings[1],$ratings[2] TO $ratings[3],$ratings[4] ";
$ratings[0] = -99;
# Make the computer's move. If the piece finds its way to the
# end of the board, crown it.
LOOP1240: {
if ($ratings[4] == 0)
{
$board[$ratings[3]][$ratings[4]] = -2;
last LOOP1240;
}
$board[$ratings[3]][$ratings[4]] = $board[$ratings[1]][$ratings[2]];
$board[$ratings[1]][$ratings[2]] = 0;
# If the piece has jumped 2 squares, it means the computer has
# taken an opponents' piece.
if (abs($ratings[1] - $ratings[3]) == 2)
{
$board [($ratings[1]+$ratings[3])/2] [($ratings[2]+$ratings[4])/2] = 0; # Delete the opponent's piece
# See if we can jump again. Evaluate all possible moves.
my $x = $ratings[3];
my $y = $ratings[4];
for (my $a = -2 ; $a <= 2 ; $a += 4)
{
if ($board[$x][$y] == -1)
{
$b = -2;
eval_move($x, $y, $a, $b);
}
if ($board[$x][$y] == -2)
{
for (my $b = -2 ; $b <= 2 ; $b += 4) { eval_move($x, $y, $a, $b); }
}
}
# If we've found a move, go back and make that one as well
if ($ratings[0] != -99)
{
print "TO $ratings[3], $ratings[4] ";
$ratings[0] = -99;
next LOOP1240;
}
}
} # LOOP1240
# Now, print the board
print "\n\n\n";
for (my $y = 7 ; $y >= 0 ; $y--)
{
my $line = "";
$line = "$y|" if ($upgrade);
for my $x (0 .. 7)
{
my $c = $chars[$board[$x][$y] + 2];
$c = ' ' if ($upgrade && (($y % 2 == 0 && $x % 2 == 1) || ($y % 2 == 1 && $x % 2 == 0)));
$line = tab($line, 5*$x+7, $c);
}
print $line;
print " \n\n";
}
print " _ _ _ _ _ _ _ _\n" if ($upgrade);
print " 0 1 2 3 4 5 6 7\n" if ($upgrade);
print "\n";
# Check if either player is out of pieces. If so, announce the
# winner.
my ($z, $t) = (0, 0);
for my $x (0 .. 7)
{
for my $y (0 .. 7)
{
if ($board[$x][$y] == 1 || $board[$x][$y] == 2) { $z = 1; }
if ($board[$x][$y] == -1 || $board[$x][$y] == -2) { $t = 1; }
}
}
if ($z != 1) { $winner = "comp"; last; }
if ($t != 1) { $winner = "you"; last; }
# Prompt the player for their move.
($z, $t) = (0, 0);
my ($x, $y, $e, $h, $a, $b);
do {
($e,$h) = get_pos("FROM:");
$x = $e;
$y = $h;
} while ($board[$x][$y] <= 0);
do {
($a,$b) = get_pos("TO:");
$x = $a;
$y = $b;
} while (!($board[$x][$y] == 0 && abs($a-$e) <= 2 && abs($a-$e) == abs($b-$h)));
LOOP1750: {
# Make the move and stop unless it might be a jump.
$board[$a][$b] = $board[$e][$h];
$board[$e][$h] = 0;
if (abs($e-$a) != 2) { last LOOP1750; }
# Remove the piece jumped over
$board[($e+$a)/2][($h+$b)/2] = 0;
# Prompt for another move; -1 means player can't, so I've won.
# Keep prompting until there's a valid move or the player gives
# up.
my ($a1, $b1);
do {
($a1,$b1) = get_pos("+TO:");
if ($a1 < 0) { last LOOP1750; }
} while ($board[$a1][$b1] != 0 || abs($a1-$a) != 2 || abs($b1-$b) != 2);
# Update the move variables to correspond to the next jump
$e = $a;
$h = $b;
$a = $a1;
$b = $b1;
}
# If the player has reached the end of the board, crown this piece
if ($b == 7) { $board[$a][$b] = 2; }
# And play the next turn.
}
# Endgame:
print "\n", ($winner eq "you" ? "YOU" : "I"), " WIN\n";
exit(0);
###########################################
# make sure we get a 2 value position
sub get_pos
{
my $prompt = shift;
my ($p1, $p2);
do {
print "$prompt ";
chomp(my $ans = <>);
($p1,$p2) = split(/[, ]/, $ans);
} while (!defined($p1) || !defined($p2) || $p1 < -1 || $p2 < -1 || $p1 > 7 || $p2 > 7);
return ($p1,$p2);
}
# deal with basic's tab() for line positioning
# line = line string we're starting with
# pos = position to start writing
# s = string to write
# returns the resultant string, which might not have been changed
sub tab
{
my ($line, $pos, $str) = @_;
my $len = length($line);
# if curser is past position, do nothing
if ($len <= $pos) { $line .= " " x ($pos - $len) . $str; }
return $line;
}
# Analyze a move from (x,y) to (x+a, y+b) and schedule it if it's
# the best candidate so far.
sub find_move
{
my ($x, $y, $a, $b) = @_;
my $u = $x+$a;
my $v = $y+$b;
# Done if it's off the board
return if ($u < 0 || $u > 7 || $v < 0 || $ v> 7);
# Consider the destination if it's empty
eval_jump($x, $y, $u, $v) if ($board[$u][$v] == 0);
# If it's got an opponent's piece, jump it instead
if ($board[$u][$v] > 0)
{
# Restore u and v, then return if it's off the board
$u += $a;
$v += $b;
return if ($u < 0 || $v < 0 || $u > 7 || $v > 7);
# Otherwise, consider u,v
eval_jump($x, $y, $u, $v) if ($board[$u][$v] == 0);
}
}
# Evaluate jumping (x,y) to (u,v).
#
# Computes a score for the proposed move and if it's higher
# than the best-so-far move, uses that instead by storing it
# and its score in @ratings.
sub eval_jump
{
my ($x, $y, $u, $v) = @_;
# q is the score; it starts at 0
my $q = 0;
# +2 if it promotes this piece
$q += 2 if ($v == 0 && $board[$x][$y] == -1);
# +5 if it takes an opponent's piece
$q += 5 if (abs($y-$v) == 2);
# -2 if the piece is moving away from the top boundary
$q -= 2 if ($y == 7);
# +1 for putting the piece against a vertical boundary
$q++ if ($u == 0 || $u == 7);
for (my $c = -1 ; $c <= 1 ; $c += 2)
{
next if ($u+$c < 0 || $u+$c > 7 || $v+$neg1 < 0);
# +1 for each adjacent friendly piece
if ($board[$u+$c][$v+$neg1] < 0)
{
$q++;
next;
}
# Prevent out-of-bounds testing
next if ($u-$c < 0 || $u-$c > 7 || $v-$neg1 > 7);
# -2 for each opponent piece that can now take this piece here
$q -= 2 if ($board[$u+$c][$v+$neg1] > 0 && ($board[$u-$c][$v-$neg1] == 0 || ($u-$c == $x && $v-$neg1 == $y)));
}
# Use this move if it's better than the previous best
if ($q > $ratings[0])
{
$ratings[0] = $q;
$ratings[1] = $x;
$ratings[2] = $y;
$ratings[3] = $u;
$ratings[4] = $v;
}
}
# If (u,v) is in the bounds, evaluate it as a move using
# the sub at 910, so storing eval in @ratings.
sub eval_move
{
my ($x, $y, $a, $b) = @_;
my $u = $x+$a;
my $v = $y+$b;
return if ($u < 0 || $u > 7 || $v < 0 || $v > 7);
eval_jump($x, $y, $u, $v) if ($board[$u][$v] == 0 && $board[$x+$a/2][$y+$b/2] > 0);
}
+202
View File
@@ -0,0 +1,202 @@
#!/usr/bin/perl
# Combat program in Perl
# Translated by Kevin Brannen (kbrannen)
use strict;
use warnings;
# globals
my $User_army;
my $User_navy;
my $User_AF;
my $Comp_army = 30000;
my $Comp_navy = 20000;
my $Comp_AF = 22000;
my $Attack_type;
my $Attack_num;
print "\n";
print " " x 33, "COMBAT\n";
print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n";
print "I AM AT WAR WITH YOU.\nWE HAVE 72000 SOLDIERS APIECE.\n\n";
do {
print "DISTRIBUTE YOUR FORCES.\n";
print "\tME\t YOU\n";
print "ARMY\t$Comp_army\t";
chomp($User_army = <>);
print "NAVY\t$Comp_navy\t";
chomp($User_navy = <>);
print "A. F.\t$Comp_AF\t";
chomp($User_AF = <>);
} while ($User_army + $User_navy + $User_AF > 72000);
do {
print "YOU ATTACK FIRST. TYPE (1) FOR ARMY; (2) FOR NAVY;\n";
print "AND (3) FOR AIR FORCE.\n";
chomp($Attack_num = <>);
} while ($Attack_type < 1 && $Attack_type > 3);
do {
print "HOW MANY MEN\n";
chomp($Attack_type = <>);
} while ($Attack_type < 0
|| ($Attack_num == 1 && $Attack_type > $User_army)
|| ($Attack_num == 2 && $Attack_type > $User_navy)
|| ($Attack_num == 3 && $Attack_type > $User_AF));
if ($Attack_num == 1)
{
if ($Attack_type<$User_army/3)
{
print "YOU LOST $Attack_type MEN FROM YOUR ARMY.\n";
$User_army = int($User_army-$Attack_type);
}
if ($Attack_type<2*$User_army/3)
{
print "YOU LOST ", int($Attack_type/3), " MEN, BUT I LOST ", int(2*$Comp_army/3), "\n";
$User_army = int($User_army-$Attack_type/3);
$Comp_army = int(2*$Comp_army/3);
}
else
{
s270();
}
}
elsif ($Attack_num == 2)
{
if ($Attack_type < $Comp_navy/3)
{
print "YOUR ATTACK WAS STOPPED!\n";
$User_navy = int($User_navy-$Attack_type);
}
if ($Attack_type < 2*$Comp_navy/3)
{
print "YOU DESTROYED ", int(2*$Comp_navy/3), " OF MY ARMY.\n";
$Comp_navy = int(2*$Comp_navy/3);
}
else
{
s270();
}
}
else # $Attack_num == 3
{
if ($Attack_type < $User_AF/3)
{
print "YOUR ATTACK WAS WIPED OUT.\n";
$User_AF = int($User_AF-$Attack_type);
}
if ($Attack_type < 2*$User_AF/3)
{
print "WE HAD A DOGFIGHT. YOU WON - AND FINISHED YOUR MISSION.\n";
$Comp_army = int(2*$Comp_army/3);
$Comp_navy = int($Comp_navy/3);
$Comp_AF = int($Comp_AF/3);
}
else
{
print "YOU WIPED OUT ONE OF MY ARMY PATROLS, BUT I DESTROYED\n";
print "TWO NAVY BASES AND BOMBED THREE ARMY BASES.\n";
$User_army = int($User_army/4);
$User_navy = int($User_navy/3);
$User_AF = int(2*$User_AF/3);
}
}
print "\n\tYOU\tME\n";
print "ARMY\t$User_army\t$Comp_army\n";
print "NAVY\t$User_navy\t$Comp_navy\n";
print "A. F.\t$User_AF\t$Comp_AF\n";
do {
print "WHAT IS YOUR NEXT MOVE?\n";
print "ARMY=1 NAVY=2 AIR FORCE=3\n";
chomp($Attack_type = <>);
} while ($Attack_type < 1 && $Attack_type > 3);
do {
print "HOW MANY MEN\n";
chomp($Attack_num = <>);
} while ($Attack_num < 0
|| ($Attack_type == 1 && $Attack_num > $User_army)
|| ($Attack_type == 2 && $Attack_num > $User_navy)
|| ($Attack_type == 3 && $Attack_num > $User_AF));
if ($Attack_num == 1)
{
if ($Attack_num < $Comp_army/2)
{
print "I WIPED OUT YOUR ATTACK!\n";
$User_army -= $Attack_num;
}
else
{
print "YOU DESTROYED MY ARMY!\n";
$Comp_army = 0;
}
}
elsif ($Attack_num == 2)
{
if ($Attack_num < $Comp_navy/2)
{
print "I SUNK TWO OF YOUR BATTLESHIPS, AND MY AIR FORCE\n";
print "WIPED OUT YOUR UNGAURDED CAPITOL.\n";
$User_army /= 4;
$User_navy /= 2;
}
else
{
print "YOUR NAVY SHOT DOWN THREE OF MY XIII PLANES,\n";
print "AND SUNK THREE BATTLESHIPS.\n";
$Comp_AF = 2*$Comp_AF/3;
$Comp_navy /= 2;
}
}
else # $Attack_num == 3
{
if ($Attack_num > $Comp_AF/2)
{
print "MY NAVY AND AIR FORCE IN A COMBINED ATTACK LEFT\n";
print "YOUR COUNTRY IN SHAMBLES.\n";
$User_army /= 3;
$User_navy /= 3;
$User_AF /= 3;
}
else
{
print "ONE OF YOUR PLANES CRASHED INTO MY HOUSE. I AM DEAD.\n";
print "MY COUNTRY FELL APART.\n";
$Comp_army = $Comp_navy = $Comp_AF = 0;
}
}
print "\nFROM THE RESULTS OF BOTH OF YOUR ATTACKS,\n";
my $total_user = $User_army+$User_navy+$User_AF;
my $total_comp = $Comp_army+$Comp_navy+$Comp_AF;
if ($total_user > 3/2*($total_comp))
{
print "YOU WON, OH! SHUCKS!!!!\n";
}
elsif ($total_user < 2/3*($total_comp))
{
print "YOU LOST-I CONQUERED YOUR COUNTRY. IT SERVES YOU\n";
print "RIGHT FOR PLAYING THIS STUPID GAME!!!\n";
}
else
{
print "THE TREATY OF PARIS CONCLUDED THAT WE TAKE OUR\n";
print "RESPECTIVE COUNTRIES AND LIVE IN PEACE.\n";
}
print "\n";
exit(0);
#######################################################
sub s270
{
print "YOU SUNK ONE OF MY PATROL BOATS, BUT I WIPED OUT TWO\n";
print "OF YOUR AIR FORCE BASES AND 3 ARMY BASES.\n";
$User_army = int($User_army/3);
$User_AF = int($User_AF/3);
$Comp_navy = int(2*$Comp_navy/3);
}
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/perl
# Digits program in Perl
# Translated by Kevin Brannen (kbrannen)
use strict;
use warnings;
# globals
my $Answer;
print "\n";
print " " x 33, "DIGITS";
print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n";
print "THIS IS A GAME OF GUESSING.\n";
print "FOR INSTRUCTIONS, TYPE '1', ELSE TYPE '0': ";
chomp($Answer = <>);
if ($Answer == 1)
{
print "\nPLEASE TAKE A PIECE OF PAPER AND WRITE DOWN\n";
print "THE DIGITS '0', '1', OR '2' THIRTY TIMES AT RANDOM.\n";
print "ARRANGE THEM IN THREE LINES OF TEN DIGITS EACH.\n";
print "I WILL ASK FOR THEN TEN AT A TIME.\n";
print "I WILL ALWAYS GUESS THEM FIRST AND THEN LOOK AT YOUR\n";
print "NEXT NUMBER TO SEE IF I WAS RIGHT. BY PURE LUCK,\n";
print "I OUGHT TO BE RIGHT TEN TIMES. BUT I HOPE TO DO BETTER\n";
print "THAN THAT. *****\n\n\n";
}
my ($A, $B, $C) = (0, 1, 3);
my (@M, @K, @L); # DIM M(26,2),K(2,2),L(8,2)
while (1)
{
for my $i (0 .. 26) { for my $j (0 .. 2) { $M[$i][$j] = 1; } }
for my $i (0 .. 2) { for my $j (0 .. 2) { $K[$i][$j] = 9; } }
for my $i (0 .. 8) { for my $j (0 .. 2) { $L[$i][$j] = 3; } }
$L[0][0] = $L[4][1] = $L[8][2] = 2;
my $Z = 26;
my $Z1 = 8;
my $Z2 = 2;
my $X = 0;
my @N;
for my $T (1 .. 3)
{
my $have_input = 0;
while (!$have_input)
{
$have_input = 1;
print "\nTEN NUMBERS, PLEASE: ";
chomp($Answer = <>);
$Answer = "0 " . $Answer; # need to be 1-based, so prepend a throw-away value for [0]
@N = split(/\s+/, $Answer);
for my $i (1 .. 10)
{
if (!defined($N[$i]) || ($N[$i] != 0 && $N[$i] != 1 && $N[$i] != 2))
{
print "ONLY USE THE DIGITS '0', '1', OR '2'.\n";
print "LET'S TRY AGAIN.";
$have_input = 0;
last;
}
}
}
print "\nMY GUESS\tYOUR NO.\tRESULT\tNO. RIGHT\n\n";
for my $U (1 .. 10)
{
my $num = $N[$U];
my $S = 0;
my $G;
for my $J (0 .. 2)
{
my $S1 = $A * $K[$Z2][$J] + $B * $L[$Z1][$J] + $C * $M[$Z][$J];
next if ($S > $S1);
if ($S >= $S1)
{
next if (rand(1) < .5);
}
$S = $S1;
$G = $J;
} # NEXT J
print " $G\t\t$N[$U]\t\t";
if ($G == $N[$U])
{
$X++;
print "RIGHT\t$X\n";
$M[$Z][$num]++;
$L[$Z1][$num]++;
$K[$Z2][$num]++;
$Z -= int($Z / 9) * 9;
$Z = 3 * $Z + $N[$U];
}
else
{
print "WRONG\t$X\n";
}
$Z1 = $Z - int($Z / 9) * 9;
$Z2 = $N[$U];
} # NEXT U
} # NEXT T
print "\n";
if ($X == 10)
{
print "I GUESSED EXACTLY 1/3 OF YOUR NUMBERS.\n";
print "IT'S A TIE GAME.\n";
}
elsif ($X > 10)
{
print "I GUESSED MORE THAN 1/3, OR $X, OF YOUR NUMBERS.\n";
print "I WIN.\a\a\a\a\a\a\a\a\a\a"
}
else
{
print "I GUESSED LESS THAN 1/3, OR $X, OF YOUR NUMBERS.\n";
print "YOU BEAT ME. CONGRATULATIONS *****\n";
}
print "\nDO YOU WANT TO TRY AGAIN (1 FOR YES, 0 FOR NO): ";
chomp($Answer = <>);
last if ($Answer != 1);
}
print "\nTHANKS FOR THE GAME.\n";
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/perl
# Flip Flop program in Perl
# Translated by Kevin Brannen (kbrannen)
use strict;
use warnings;
use Math::Trig;
print "\n";
print " " x 32, "FLIPFLOP";
print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n";
# *** CREATED BY MICHAEL CASS
print "THE OBJECT OF THIS PUZZLE IS TO CHANGE THIS:\n\n";
print "X X X X X X X X X X\n\n";
print "TO THIS:\n\n";
print "O O O O O O O O O O\n\n";
print "BY TYPING THE NUMBER CORRESPONDING TO THE POSITION OF THE\n";
print "LETTER ON SOME NUMBERS, ONE POSITION WILL CHANGE, ON\n";
print "OTHERS, TWO WILL CHANGE. TO RESET LINE TO ALL X'S, TYPE 0\n";
print "(ZERO) AND TO START OVER IN THE MIDDLE OF A GAME, TYPE \n";
print "11 (ELEVEN).\n\n";
sub initialize
{
my @a;
print "1 2 3 4 5 6 7 8 9 10\n";
print "X X X X X X X X X X\n";
for my $i (0 .. 10) { $a[$i] = "X"; } # make sure [0] has a value just in case
return @a;
}
while (1)
{
my $Q = rand(1);
my $C = 0;
print "HERE IS THE STARTING LINE OF X'S.\n\n";
my @A = initialize();
while (1)
{
my $M = 0;
my $N;
while (1)
{
print "\nINPUT THE NUMBER: ";
chomp($N = <>);
if ($N != int($N) || $N < 0 || $N > 11)
{
print "ILLEGAL ENTRY--TRY AGAIN.\n";
next;
}
last;
}
if ($N == 11) # start a new game
{
print "\n\n";
last;
}
if ($N == 0) # reset line
{
@A = initialize();
next;
}
if ($M != $N)
{
$M = $N;
$A[$N] = ($A[$N] eq "O") ? "X" : "O";
while ($M == $N)
{
my $R = tan($Q + $N / $Q - $N) - sin($Q / $N) + 336 * sin(8 * $N);
$N = $R - int($R);
$N = int(10 * $N);
if ($A[$N] eq "O")
{
$A[$N] = "X";
next;
}
$A[$N] = "O";
last; # GOTO 610
$A[$N] = "X";
}
}
else
{
if ($A[$N] ne "O") { $A[$N] = "O"; }
while ($M == $N)
{
my $R = .592 * (1 / tan($Q / $N + $Q)) / sin( $N * 2 + $Q) - cos($N);
$N = $R - int($R);
$N = int(10 * $N);
if ($A[$N] eq "O")
{
$A[$N] = "X";
next;
}
$A[$N] = "O";
last;
}
}
print "1 2 3 4 5 6 7 8 9 10\n";
for my $i (1 .. 10) { print "$A[$i] "; }
print "\n";
$C++;
my $i;
for ($i=1 ; $i <= 10 ; $i++) {
last if ($A[$i] ne "O");
}
if ($i == 11)
{
if ($C <= 12) { print "VERY GOOD. YOU GUESSED IT IN ONLY $C GUESSES.\n"; }
else { print "TRY HARDER NEXT TIME. IT TOOK YOU $C GUESSES.\n"; }
last;
}
}
print "DO YOU WANT TO TRY ANOTHER PUZZLE (Y/N): ";
$_ = <>;
print "\n";
last if (m/^n/i);
}
+2
View File
@@ -1,3 +1,5 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Perl](https://www.perl.org/)
You can answer yes/no questions in lower case if desired.
+265
View File
@@ -0,0 +1,265 @@
#!/usr/bin/perl
# Fur Trader program in Perl
# Translated by Kevin Brannen (kbrannen)
use strict;
use warnings;
# globals
my @Pelts = (qw(0 MINK BEAVER ERMINE FOX ));
my $Num_pelts = 4;
my @Quantity; # how many of each fur
my $Money;
my $Max_pelts = 190;
my $Ermine_price; # like we have @Pelts and @Quantity we could have @Prices
my $Beaver_price; # then have 4 constants as index into the arrays to avoid
my $Fox_price; # the magic numbers 1-4, or better have a array of objects (really a hash)
my $Mink_price; # with the 3 keys (name, number, price), but well keep it
# with 4 vars like the basic program did
print "\n";
print " " x 31, "FUR TRADER\n";
print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n";
init();
while (1)
{
my $ans = get_yesno();
last if ($ans ne "YES");
$Ermine_price = new_price(.15, 0.95);
$Beaver_price = new_price(.25, 1.00);
print "\nYOU HAVE \$$Money SAVINGS.\n";
print "AND $Max_pelts FURS TO BEGIN THE EXPEDITION.\n";
print "\nYOUR $Max_pelts FURS ARE DISTRIBUTED AMONG THE FOLLOWING\n";
print "KINDS OF PELTS: MINK, BEAVER, ERMINE AND FOX.\n";
reset_furs();
my $total = 0;
for my $j ( 1 .. $Num_pelts)
{
print "\nHOW MANY $Pelts[$j] PELTS DO YOU HAVE? ";
chomp(my $ans = <>);
$Quantity[$j] = int($ans);
$total += $Quantity[$j];
}
if ($total > $Max_pelts)
{
print "\nYOU MAY NOT HAVE THAT MANY FURS.\n";
print "DO NOT TRY TO CHEAT. I CAN ADD.\n";
print "YOU MUST START AGAIN.\n";
init();
next;
}
print "\nYOU MAY TRADE YOUR FURS AT FORT 1, FORT 2,\n";
print "OR FORT 3. FORT 1 IS FORT HOCHELAGA (MONTREAL)\n";
print "AND IS UNDER THE PROTECTION OF THE FRENCH ARMY.\n";
print "FORT 2 IS FORT STADACONA (QUEBEC) AND IS UNDER THE\n";
print "PROTECTION OF THE FRENCH ARMY. HOWEVER, YOU MUST\n";
print "MAKE A PORTAGE AND CROSS THE LACHINE RAPIDS.\n";
print "FORT 3 IS FORT NEW YORK AND IS UNDER DUTCH CONTROL.\n";
print "YOU MUST CROSS THROUGH IROQUOIS LAND.\n";
my $done = 0;
while (!$done)
{
$ans = 0;
while ($ans < 1 || $ans > 3)
{
no warnings; # in case user enters alpha chars, then int() will return 0 with no warnings
print "ANSWER 1, 2, OR 3: ";
$ans = int(<>);
}
# returns 0 if they want to go somewhere else;
# or returns the old basic line number to show what to do
if ($ans == 1) { $done = fort1(); }
elsif ($ans == 2) { $done = fort2(); }
elsif ($ans == 3) { $done = fort3(); }
}
if ($done == 1410)
{
print "YOUR BEAVER SOLD FOR \$", $Beaver_price * $Quantity[2], "\t";
}
if ($done <= 1414)
{
print "YOUR FOX SOLD FOR \$", $Fox_price * $Quantity[4], "\n";
print "YOUR ERMINE SOLD FOR \$", $Ermine_price * $Quantity[3], "\t";
print "YOUR MINK SOLD FOR \$", $Mink_price * $Quantity[1], "\n";
}
# 1418 is always done
$Money += $Mink_price * $Quantity[1] + $Beaver_price * $Quantity[2] + $Ermine_price * $Quantity[3] + $Fox_price * $Quantity[4];
print "\nYOU NOW HAVE \$$Money INCLUDING YOUR PREVIOUS SAVINGS\n";
print "\nDO YOU WANT TO TRADE FURS NEXT YEAR? ";
}
exit(0);
###############################################################
sub init
{
print "YOU ARE THE LEADER OF A FRENCH FUR TRADING EXPEDITION IN\n";
print "1776 LEAVING THE LAKE ONTARIO AREA TO SELL FURS AND GET\n";
print "SUPPLIES FOR THE NEXT YEAR. YOU HAVE A CHOICE OF THREE\n";
print "FORTS AT WHICH YOU MAY TRADE. THE COST OF SUPPLIES\n";
print "AND THE AMOUNT YOU RECEIVE FOR YOUR FURS WILL DEPEND\n";
print "ON THE FORT THAT YOU CHOOSE.\n";
$Money = 600;
print "DO YOU WISH TO TRADE FURS?\n";
}
sub new_price
{
my ($base, $factor) = @_;
return int(($base * rand(1) + $factor) * 100 + .5) / 100;
}
sub supplies_fs
{
print "SUPPLIES AT FORT STADACONA COST \$125.00.\n";
print "YOUR TRAVEL EXPENSES TO STADACONA WERE \$15.00.\n";
}
sub supplies_ny
{
print "SUPPLIES AT NEW YORK COST \$80.00.\n";
print "YOUR TRAVEL EXPENSES TO NEW YORK WERE \$25.00.\n";
}
sub reset_furs
{
for my $j (1 .. $Num_pelts) { $Quantity[$j] = 0; }
}
sub get_yesno
{
my $ans;
print "ANSWER YES OR NO: ";
chomp($ans = uc(<>));
return $ans;
}
sub trade_elsewhere
{
print "DO YOU WANT TO TRADE AT ANOTHER FORT? ";
my $ans = get_yesno();
return $ans;
}
sub fort1
{
print "\nYOU HAVE CHOSEN THE EASIEST ROUTE. HOWEVER, THE FORT\n";
print "IS FAR FROM ANY SEAPORT. THE VALUE\n";
print "YOU RECEIVE FOR YOUR FURS WILL BE LOW AND THE COST\n";
print "OF SUPPLIES HIGHER THAN AT FORTS STADACONA OR NEW YORK.\n";
my $ans = trade_elsewhere();
if ($ans eq "YES") { return 0; }
$Money -= 160;
$Mink_price = new_price(.2, .7 );
$Ermine_price = new_price(.2, .65);
$Beaver_price = new_price(.2, .75);
$Fox_price = new_price(.2, .8 );
print "\nSUPPLIES AT FORT HOCHELAGA COST \$150.00.\n";
print "YOUR TRAVEL EXPENSES TO HOCHELAGA WERE \$10.00.\n";
return 1410;
}
sub fort2
{
print "\nYOU HAVE CHOSEN A HARD ROUTE. IT IS, IN COMPARSION,\n";
print "HARDER THAN THE ROUTE TO HOCHELAGA BUT EASIER THAN\n";
print "THE ROUTE TO NEW YORK. YOU WILL RECEIVE AN AVERAGE VALUE\n";
print "FOR YOUR FURS AND THE COST OF YOUR SUPPLIES WILL BE AVERAGE.\n";
my $ans = trade_elsewhere();
if ($ans eq "YES") { return 0; }
$Money -= 140;
print "\n";
$Mink_price = new_price(.3, .85);
$Ermine_price = new_price(.15, .8);
$Beaver_price = new_price(.2, .9);
my $P = int(10 * rand(1)) + 1;
if ($P <= 2)
{
$Quantity[2] = 0;
print "YOUR BEAVER WERE TOO HEAVY TO CARRY ACROSS\n";
print "THE PORTAGE. YOU HAD TO LEAVE THE PELTS, BUT FOUND\n";
print "THEM STOLEN WHEN YOU RETURNED.\n";
supplies_fs();
return 1414;
}
elsif ($P <= 6)
{
print "YOU ARRIVED SAFELY AT FORT STADACONA.\n";
supplies_fs();
}
elsif ($P <= 8)
{
reset_furs();
print "YOUR CANOE UPSET IN THE LACHINE RAPIDS. YOU\n";
print "LOST ALL YOUR FURS.\n";
supplies_fs();
return 1418;
}
elsif ($P <= 10)
{
$Quantity[4] = 0;
print "YOUR FOX PELTS WERE NOT CURED PROPERLY.\n";
print "NO ONE WILL BUY THEM.\n";
supplies_fs();
}
return 1410;
}
sub fort3
{
print "\nYOU HAVE CHOSEN THE MOST DIFFICULT ROUTE. AT\n";
print "FORT NEW YORK YOU WILL RECEIVE THE HIGHEST VALUE\n";
print "FOR YOUR FURS. THE COST OF YOUR SUPPLIES\n";
print "WILL BE LOWER THAN AT ALL THE OTHER FORTS.\n";
my $ans = trade_elsewhere();
if ($ans eq "YES") { return 0; }
$Money -= 105;
print "\n";
$Mink_price = new_price(.15, 1.05);
$Fox_price = new_price(.25, 1.1);
$Fox_price = new_price(.25, 1.1);
my $P = int(10 * rand(1)) + 1;
if ($P <= 2)
{
print "YOU WERE ATTACKED BY A PARTY OF IROQUOIS.\n";
print "ALL PEOPLE IN YOUR TRADING GROUP WERE\n";
print "KILLED. THIS ENDS THE GAME.\n";
exit(0);
}
elsif ($P <= 6)
{
print "YOU WERE LUCKY. YOU ARRIVED SAFELY\n";
print "AT FORT NEW YORK.\n";
supplies_ny();
}
elsif ($P <= 8)
{
reset_furs();
print "YOU NARROWLY ESCAPED AN IROQUOIS RAIDING PARTY.\n";
print "HOWEVER, YOU HAD TO LEAVE ALL YOUR FURS BEHIND.\n";
supplies_ny();
return 1418;
}
elsif ($P <= 10)
{
$Beaver_price /= 2;
$Mink_price /= 2;
print "YOUR MINK AND BEAVER WERE DAMAGED ON YOUR TRIP.\n";
print "YOU RECEIVE ONLY HALF THE CURRENT PRICE FOR THESE FURS.\n";
supplies_ny();
}
return 1410;
}
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/perl
# Gunner program in Perl
# Required extensive restructuring to remove all of the GOTO's.
# Translated by Kevin Brannen (kbrannen)
use strict;
use warnings;
# globals
my $Max_range = int(40000*rand(1)+20000);
my $Total_shots = 0;
my $Games = 0;
print "\n";
print " " x 30, "GUNNER\n";
print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n";
print "YOU ARE THE OFFICER-IN-CHARGE, GIVING ORDERS TO A GUN\n";
print "CREW, TELLING THEM THE DEGREES OF ELEVATION YOU ESTIMATE\n";
print "WILL PLACE A PROJECTILE ON TARGET. A HIT WITHIN 100 YARDS\n";
print "OF THE TARGET WILL DESTROY IT.\n\n";
print "MAXIMUM RANGE OF YOUR GUN IS $Max_range YARDS.\n\n";
GAME: while (1)
{
my $target_dist = int($Max_range * (.1 + .8 * rand(1)));
my $shots = 0;
print "DISTANCE TO THE TARGET IS $target_dist YARDS.\n\n";
while (1)
{
my $elevation = get_elevation(); # in degrees
$shots++;
my $dist = int($target_dist - ($Max_range * sin(2 * $elevation / 57.3)));
if (abs($dist) < 100)
{
print "*** TARGET DESTROYED *** $shots ROUNDS OF AMMUNITION EXPENDED.\n";
$Total_shots += $shots;
if ($Games++ == 4)
{
print "\n\nTOTAL ROUNDS EXPENDED WERE: $Total_shots\n";
if ($Total_shots > 18) { print "BETTER GO BACK TO FORT SILL FOR REFRESHER TRAINING!\n"; }
else { print "NICE SHOOTING !!\n"; }
last;
}
print "\nTHE FORWARD OBSERVER HAS SIGHTED MORE ENEMY ACTIVITY...\n";
next GAME;
}
if ($dist > 100) { print "SHORT OF TARGET BY ", abs($dist)," YARDS.\n"; }
else { print "OVER TARGET BY ", abs($dist), " YARDS.\n"; }
if ($shots >= 5)
{
print "\nBOOM !!!! YOU HAVE JUST BEEN DESTROYED BY THE ENEMY.\n\n\n\n";
print "BETTER GO BACK TO FORT SILL FOR REFRESHER TRAINING!\n";
last;
}
}
print "\nTRY AGAIN (Y OR N): ";
chomp(my $ans=uc(<>));
if ($ans ne "Y") { last; }
else { $Games = 0; $Total_shots = 0; }
}
print "\nOK. RETURN TO BASE CAMP.\n";
####################################
sub get_elevation
{
my $elevation;
while (1)
{
print "\nELEVATION: ";
chomp($elevation = <>);
if ($elevation > 89) { print "MAXIMUM ELEVATION IS 89 DEGREES.\n"; }
elsif ($elevation < 1) { print "MINIMUM ELEVATION IS ONE DEGREE.\n"; }
else { return $elevation; }
}
}
+14 -2
View File
@@ -1,10 +1,10 @@
## King
This is one of the most comprehensive, difficult, and interesting games. (If youve never played one of these games, start with HAMMURABI.)
This is one of the most comprehensive, difficult, and interesting games. (If you've never played one of these games, start with HAMMURABI.)
In this game, you are Premier of Setats Detinu, a small communist island 30 by 70 miles long. Your job is to decide upon the budget of your country and distribute money to your country from the communal treasury.
The money system is Rollods; each person needs 100 Rallods per year to survive. Your countrys income comes from farm produce and tourists visiting your magnificent forests, hunting, fishing, etc. Part of your land is farm land but it also has an excellent mineral content and may be sold to foreign industry for strip mining. Industry import and support their own workers. Crops cost between 10 and 15 Rallods per square mile to plant, cultivate, and harvest. Your goal is to complete an eight-year term of office without major mishap. A word of warning: it isnt easy!
The money system is Rollods; each person needs 100 Rallods per year to survive. Your country's income comes from farm produce and tourists visiting your magnificent forests, hunting, fishing, etc. Part of your land is farm land but it also has an excellent mineral content and may be sold to foreign industry for strip mining. Industry import and support their own workers. Crops cost between 10 and 15 Rallods per square mile to plant, cultivate, and harvest. Your goal is to complete an eight-year term of office without major mishap. A word of warning: it isn't easy!
The author of this program is James A. Storer who wrote it while a student at Lexington High School.
@@ -66,3 +66,15 @@ On basic line 1997 it is:
but it should be:
1997 PRINT " AND 1,000 SQ. MILES OF FOREST LAND."
### Bug 4
On basic line 1310 we see this:
1310 IF C=0 THEN 1324
1320 PRINT "OF ";INT(J);"SQ. MILES PLANTED,";
1324 ...
but it should probably be:
1310 IF J=0 THEN 1324
+133
View File
@@ -0,0 +1,133 @@
namespace King;
internal class Country
{
private const int InitialLand = 1000;
private readonly IReadWrite _io;
private readonly IRandom _random;
private float _rallods;
private float _countrymen;
private float _foreigners;
private float _arableLand;
private float _industryLand;
public Country(IReadWrite io, IRandom random)
: this(
io,
random,
(int)(60000 + random.NextFloat(1000) - random.NextFloat(1000)),
(int)(500 + random.NextFloat(10) - random.NextFloat(10)),
0,
InitialLand)
{
}
public Country(IReadWrite io, IRandom random, float rallods, float countrymen, float foreigners, float land)
{
_io = io;
_random = random;
_rallods = rallods;
_countrymen = countrymen;
_foreigners = foreigners;
_arableLand = land;
}
public string GetStatus(int landValue, int plantingCost)
=> Resource.Status(_rallods, _countrymen, _foreigners, _arableLand, landValue, plantingCost);
public float Countrymen => _countrymen;
public float Workers => _foreigners;
public bool HasWorkers => _foreigners > 0;
private float FarmLand => _arableLand;
public bool HasRallods => _rallods > 0;
public float Rallods => _rallods;
public float IndustryLand => InitialLand - _arableLand;
public int PreviousTourismIncome { get; private set; }
public bool SellLand(int landValue, out float landSold)
{
if (_io.TryReadValue(
SellLandPrompt,
out landSold,
new ValidityTest(v => v <= FarmLand, () => SellLandError(FarmLand))))
{
_arableLand = (int)(_arableLand - landSold);
_rallods = (int)(_rallods + landSold * landValue);
return true;
}
return false;
}
public bool DistributeRallods(out float rallodsGiven)
{
if (_io.TryReadValue(
GiveRallodsPrompt,
out rallodsGiven,
new ValidityTest(v => v <= _rallods, () => GiveRallodsError(_rallods))))
{
_rallods = (int)(_rallods - rallodsGiven);
return true;
}
return false;
}
public bool PlantLand(int plantingCost, out float landPlanted)
{
if (_io.TryReadValue(
PlantLandPrompt,
out landPlanted,
new ValidityTest(v => v <= _countrymen * 2, PlantLandError1),
new ValidityTest(v => v <= FarmLand, PlantLandError2(FarmLand)),
new ValidityTest(v => v * plantingCost <= _rallods, PlantLandError3(_rallods))))
{
_rallods -= (int)(landPlanted * plantingCost);
return true;
}
return false;
}
public bool ControlPollution(out float rallodsSpent)
{
if (_io.TryReadValue(
PollutionPrompt,
out rallodsSpent,
new ValidityTest(v => v <= _rallods, () => PollutionError(_rallods))))
{
_rallods = (int)(_rallods - rallodsSpent);
return true;
}
return false;
}
public bool TrySpend(float amount, float landValue)
{
if (_rallods >= amount)
{
_rallods -= amount;
return true;
}
_arableLand = (int)(_arableLand - (int)(amount - _rallods) / landValue);
_rallods = 0;
return false;
}
public void RemoveTheDead(int deaths) => _countrymen = (int)(_countrymen - deaths);
public void Migration(int migration) => _countrymen = (int)(_countrymen + migration);
public void AddWorkers(int newWorkers) => _foreigners = (int)(_foreigners + newWorkers);
public void SellCrops(int income) => _rallods = (int)(_rallods + income);
public void EntertainTourists(int income)
{
PreviousTourismIncome = income;
_rallods = (int)(_rallods + income);
}
}
+47
View File
@@ -0,0 +1,47 @@
namespace King;
internal class Game
{
const int TermOfOffice = 8;
private readonly IReadWrite _io;
private readonly IRandom _random;
public Game(IReadWrite io, IRandom random)
{
_io = io;
_random = random;
}
public void Play()
{
_io.Write(Title);
var reign = SetUpReign();
if (reign != null)
{
while (reign.PlayYear());
}
_io.WriteLine();
_io.WriteLine();
}
private Reign? SetUpReign()
{
var response = _io.ReadString(InstructionsPrompt).ToUpper();
if (response.Equals("Again", StringComparison.InvariantCultureIgnoreCase))
{
return _io.TryReadGameData(_random, out var reign) ? reign : null;
}
if (!response.StartsWith("N", StringComparison.InvariantCultureIgnoreCase))
{
_io.Write(InstructionsText(TermOfOffice));
}
_io.WriteLine();
return new Reign(_io, _random);
}
}
+61
View File
@@ -0,0 +1,61 @@
using System.Diagnostics.CodeAnalysis;
using static King.Resources.Resource;
namespace King;
internal static class IOExtensions
{
internal static bool TryReadGameData(this IReadWrite io, IRandom random, [NotNullWhen(true)] out Reign? reign)
{
if (io.TryReadValue(SavedYearsPrompt, v => v < Reign.MaxTerm, SavedYearsError(Reign.MaxTerm), out var years) &&
io.TryReadValue(SavedTreasuryPrompt, out var rallods) &&
io.TryReadValue(SavedCountrymenPrompt, out var countrymen) &&
io.TryReadValue(SavedWorkersPrompt, out var workers) &&
io.TryReadValue(SavedLandPrompt, v => v is > 1000 and <= 2000, SavedLandError, out var land))
{
reign = new Reign(io, random, new Country(io, random, rallods, countrymen, workers, land), years + 1);
return true;
}
reign = default;
return false;
}
internal static bool TryReadValue(this IReadWrite io, string prompt, out float value, params ValidityTest[] tests)
{
while (true)
{
var response = value = io.ReadNumber(prompt);
if (response == 0) { return false; }
if (tests.All(test => test.IsValid(response, io))) { return true; }
}
}
internal static bool TryReadValue(this IReadWrite io, string prompt, out float value)
=> io.TryReadValue(prompt, _ => true, "", out value);
internal static bool TryReadValue(
this IReadWrite io,
string prompt,
Predicate<float> isValid,
string error,
out float value)
=> io.TryReadValue(prompt, isValid, () => error, out value);
internal static bool TryReadValue(
this IReadWrite io,
string prompt,
Predicate<float> isValid,
Func<string> getError,
out float value)
{
while (true)
{
value = io.ReadNumber(prompt);
if (value < 0) { return false; }
if (isValid(value)) { return true; }
io.Write(getError());
}
}
}
+8
View File
@@ -6,4 +6,12 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Resources/*.txt" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
</ItemGroup>
</Project>
+7
View File
@@ -0,0 +1,7 @@
global using Games.Common.IO;
global using Games.Common.Randomness;
global using King.Resources;
global using static King.Resources.Resource;
using King;
new Game(new ConsoleIO(), new RandomNumberGenerator()).Play();
+45
View File
@@ -0,0 +1,45 @@
namespace King;
internal class Reign
{
public const int MaxTerm = 8;
private readonly IReadWrite _io;
private readonly IRandom _random;
private readonly Country _country;
private float _yearNumber;
public Reign(IReadWrite io, IRandom random)
: this(io, random, new Country(io, random), 1)
{
}
public Reign(IReadWrite io, IRandom random, Country country, float year)
{
_io = io;
_random = random;
_country = country;
_yearNumber = year;
}
public bool PlayYear()
{
var year = new Year(_country, _random, _io);
_io.Write(year.Status);
var result = year.GetPlayerActions() ?? year.EvaluateResults() ?? IsAtEndOfTerm();
if (result.IsGameOver)
{
_io.WriteLine(result.Message);
return false;
}
return true;
}
private Result IsAtEndOfTerm()
=> _yearNumber == MaxTerm
? Result.GameOver(EndCongratulations(MaxTerm))
: Result.Continue;
}
@@ -0,0 +1 @@
{0} countrymen died of carbon-monoxide and dust inhalation
@@ -0,0 +1 @@
{0} countrymen died of starvation
+1
View File
@@ -0,0 +1 @@
{0} countrymen left the island.
+3
View File
@@ -0,0 +1,3 @@
also had your left eye gouged out!
;have also gained a very bad reputation.
;have also been declared national fink.
@@ -0,0 +1,10 @@
Congratulations!!!!!!!!!!!!!!!!!!
You have successfully completed your {0} year term
of office. You were, of course, extremely lucky, but
nevertheless, it's quite an achievement. Goodbye and good
luck - you'll probably need it if you're the type that
plays this game.
@@ -0,0 +1,3 @@
You have been thrown out of office and are now
residing in prison.;
You have been assassinated.
@@ -0,0 +1,7 @@
The number of foreign workers has exceeded the number
of countrymen. As a minority, they have revolted and
taken over the country.
{0}
+5
View File
@@ -0,0 +1,5 @@
{0} countrymen died in one year!!!!!
due to this extreme mismanagement, you have not only
been impeached and thrown out of office, but you
{1}
@@ -0,0 +1,7 @@
Money was left over in the treasury which you did
not spend. As a result, some of your countrymen died
of starvation. The public is enraged and you have
been forced to resign.
@@ -0,0 +1,7 @@
Over one third of the population has died since you
were elected to office. The people (remaining)
hate your guts.
{0}
@@ -0,0 +1 @@
You were forced to spend {0} rallods on funeral expenses
@@ -0,0 +1 @@
Think again. You've only got {0} rallods in the treasury.
@@ -0,0 +1 @@
How many rallods will you distribute among your countrymen
+4
View File
@@ -0,0 +1,4 @@
Goodbye.
(If you wish to continue this game at a later date, answer
'again' when asked if you want instructions at the start
of the game).
+2
View File
@@ -0,0 +1,2 @@
you harvested {0} sq. miles of crops.
{1}making {2} rallods.
@@ -0,0 +1 @@
(Due to increased air and water pollution from foreign industry.)
+1
View File
@@ -0,0 +1 @@
{0} countrymen came to the island.
@@ -0,0 +1 @@
Do you want instructions
@@ -0,0 +1,17 @@
Congratulations! You've just been elected Premier of Setats
Detinu, a small communist island 30 by 70 miles long. Your
job is to decide upon the country's budget and distribute
money to your countrymen from the communal treasury.
The money system is rallods, and each person needs 100
rallods per year to survive. Your country's income comes
from farm produce and tourists visiting your magnificent
forests, hunting, fishing, etc. Half your land if farm land
which also has an excellent mineral content and may be sold
to foreign industry (strip mining) who import and support
their own workers. Crops cost between 10 and 15 rallods per
square mile to plant.
Your goal is to complete your {0} year term of office.
Good luck!
+1
View File
@@ -0,0 +1 @@
Of {0} sq. miles planted,
@@ -0,0 +1 @@
Sorry, but each countryman can only plant 2 sq. miles.
@@ -0,0 +1 @@
Sorry, but you've only {0} sq. miles of farm land.
@@ -0,0 +1 @@
Think again, You've only {0} rallods left in the treasury.
@@ -0,0 +1 @@
How many square miles do you wish to plant
@@ -0,0 +1 @@
Think again. You only have {0} rallods remaining.
@@ -0,0 +1 @@
How many rallods do you wish to spend on pollution control
+112
View File
@@ -0,0 +1,112 @@
using System.Reflection;
using System.Runtime.CompilerServices;
namespace King.Resources;
internal static class Resource
{
private static bool _sellLandErrorShown;
public static Stream Title => GetStream();
public static string InstructionsPrompt => GetString();
public static string InstructionsText(int years) => string.Format(GetString(), years);
public static string Status(
float rallods,
float countrymen,
float workers,
float land,
float landValue,
float plantingCost)
=> string.Format(
workers == 0 ? StatusWithWorkers : StatusSansWorkers,
rallods,
(int)countrymen,
(int)workers,
(int)land,
landValue,
plantingCost);
private static string StatusWithWorkers => GetString();
private static string StatusSansWorkers => GetString();
public static string SellLandPrompt => GetString();
public static string SellLandError(float farmLand)
{
var error = string.Format(GetString(), farmLand, _sellLandErrorShown ? "" : SellLandErrorReason);
_sellLandErrorShown = true;
return error;
}
private static string SellLandErrorReason => GetString();
public static string GiveRallodsPrompt => GetString();
public static string GiveRallodsError(float rallods) => string.Format(GetString(), rallods);
public static string PlantLandPrompt => GetString();
public static string PlantLandError1 => GetString();
public static string PlantLandError2(float farmLand) => string.Format(GetString(), farmLand);
public static string PlantLandError3(float rallods) => string.Format(GetString(), rallods);
public static string PollutionPrompt => GetString();
public static string PollutionError(float rallods) => string.Format(GetString(), rallods);
public static string DeathsStarvation(float deaths) => string.Format(GetString(), (int)deaths);
public static string DeathsPollution(int deaths) => string.Format(GetString(), deaths);
public static string FuneralExpenses(int expenses) => string.Format(GetString(), expenses);
public static string InsufficientReserves => GetString();
public static string WorkerMigration(int newWorkers) => string.Format(GetString(), newWorkers);
public static string Migration(int migration)
=> string.Format(migration < 0 ? Emigration : Immigration, Math.Abs(migration));
public static string Emigration => GetString();
public static string Immigration => GetString();
public static string LandPlanted(float landPlanted)
=> landPlanted > 0 ? string.Format(GetString(), (int)landPlanted) : "";
public static string Harvest(int yield, int income, bool hasIndustry)
=> string.Format(GetString(), yield, HarvestReason(hasIndustry), income);
private static string HarvestReason(bool hasIndustry) => hasIndustry ? GetString() : "";
public static string TourismEarnings(int income) => string.Format(GetString(), income);
public static string TourismDecrease(IRandom random) => string.Format(GetString(), TourismReason(random));
private static string TourismReason(IRandom random) => GetStrings()[random.Next(5)];
private static string EndAlso(IRandom random)
=> random.Next(10) switch
{
<= 3 => GetStrings()[0],
<= 6 => GetStrings()[1],
_ => GetStrings()[2]
};
public static string EndCongratulations(int termLength) => string.Format(GetString(), termLength);
private static string EndConsequences(IRandom random) => GetStrings()[random.Next(2)];
public static string EndForeignWorkers(IRandom random) => string.Format(GetString(), EndConsequences(random));
public static string EndManyDead(int deaths, IRandom random) => string.Format(GetString(), deaths, EndAlso(random));
public static string EndMoneyLeftOver() => GetString();
public static string EndOneThirdDead(IRandom random) => string.Format(GetString(), EndConsequences(random));
public static string SavedYearsPrompt => GetString();
public static string SavedYearsError(int years) => string.Format(GetString(), years);
public static string SavedTreasuryPrompt => GetString();
public static string SavedCountrymenPrompt => GetString();
public static string SavedWorkersPrompt => GetString();
public static string SavedLandPrompt => GetString();
public static string SavedLandError => GetString();
public static string Goodbye => GetString();
private static string[] GetStrings([CallerMemberName] string? name = null) => GetString(name).Split(';');
private static string GetString([CallerMemberName] string? name = null)
{
using var stream = GetStream(name);
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
}
private static Stream GetStream([CallerMemberName] string? name = null) =>
Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(Resource).Namespace}.{name}.txt")
?? throw new Exception($"Could not find embedded resource stream '{name}'.");
}
@@ -0,0 +1 @@
How many countrymen
@@ -0,0 +1,2 @@
Come on, you started with 1000 sq. miles of farm land
and 10,000 sq. miles of forest land
@@ -0,0 +1 @@
How many square miles of land
@@ -0,0 +1 @@
How much did you have in the treasury
@@ -0,0 +1 @@
How many workers
@@ -0,0 +1 @@
Come on, your term in office is only {0} years.
@@ -0,0 +1 @@
How many years had you been in office when interrupted
@@ -0,0 +1,2 @@
*** Think again. You only have {0} square miles of farm land.
{1}
@@ -0,0 +1,4 @@
(Foreign industry will only buy farm land because
forest land is uneconomical to strip mine due to trees,
thicker top soil, etc.)
@@ -0,0 +1 @@
How many square miles do you wish to sell to industry
@@ -0,0 +1,6 @@
You now have {0} rallods in the treasury.
{1} countrymen, and {3} sq. miles of land.
This year industry will buy land for {4} rallods per square mile.
Land currently costs {5} rallods per square mile to plant.
@@ -0,0 +1,6 @@
You now have {0} rallods in the treasury.
{1} countrymen, {2} foreign workers and {3} sq. miles of land.
This year industry will buy land for {4} rallods per square mile.
Land currently costs {5} rallods per square mile to plant.
+5
View File
@@ -0,0 +1,5 @@
King
Creative Computing Morristown, New Jersey
@@ -0,0 +1 @@
Decrease because {0}
@@ -0,0 +1 @@
You made {0} rallods from tourist trade.
@@ -0,0 +1,5 @@
fish population has dwindled due to water pollution.
;air pollution is killing game bird population.
;mineral baths are being ruined by water pollution.
;unpleasant smog is discouraging sun bathers.
;hotels are looking shabby due to smog grit.
@@ -0,0 +1 @@
{0} workers came to the country and
+7
View File
@@ -0,0 +1,7 @@
namespace King;
internal record struct Result (bool IsGameOver, string Message)
{
internal static Result GameOver(string message) => new(true, message);
internal static Result Continue => new(false, "");
}
+26
View File
@@ -0,0 +1,26 @@
namespace King;
internal class ValidityTest
{
private readonly Predicate<float> _isValid;
private readonly Func<string> _getError;
public ValidityTest(Predicate<float> isValid, string error)
: this(isValid, () => error)
{
}
public ValidityTest(Predicate<float> isValid, Func<string> getError)
{
_isValid = isValid;
_getError = getError;
}
public bool IsValid(float value, IReadWrite io)
{
if (_isValid(value)) { return true; }
io.Write(_getError());
return false;
}
}
+154
View File
@@ -0,0 +1,154 @@
using System.Text;
namespace King;
internal class Year
{
private readonly Country _country;
private readonly IRandom _random;
private readonly IReadWrite _io;
private readonly int _plantingCost;
private readonly int _landValue;
private float _landSold;
private float _rallodsDistributed;
private float _landPlanted;
private float _pollutionControlCost;
private float _citizenSupport;
private int _deaths;
private float _starvationDeaths;
private int _pollutionDeaths;
private int _migration;
public Year(Country country, IRandom random, IReadWrite io)
{
_country = country;
_random = random;
_io = io;
_plantingCost = random.Next(10, 15);
_landValue = random.Next(95, 105);
}
public string Status => _country.GetStatus(_landValue, _plantingCost);
public Result? GetPlayerActions()
{
var playerSoldLand = _country.SellLand(_landValue, out _landSold);
var playerDistributedRallods = _country.DistributeRallods(out _rallodsDistributed);
var playerPlantedLand = _country.HasRallods && _country.PlantLand(_plantingCost, out _landPlanted);
var playerControlledPollution = _country.HasRallods && _country.ControlPollution(out _pollutionControlCost);
return playerSoldLand || playerDistributedRallods || playerPlantedLand || playerControlledPollution
? null
: Result.GameOver(Goodbye);
}
public Result? EvaluateResults()
{
var rallodsUnspent = _country.Rallods;
_io.WriteLine();
_io.WriteLine();
return EvaluateDeaths()
?? EvaluateMigration()
?? EvaluateAgriculture()
?? EvaluateTourism()
?? DetermineResult(rallodsUnspent);
}
public Result? EvaluateDeaths()
{
var supportedCountrymen = _rallodsDistributed / 100;
_citizenSupport = supportedCountrymen - _country.Countrymen;
_starvationDeaths = -_citizenSupport;
if (_starvationDeaths > 0)
{
if (supportedCountrymen < 50) { return Result.GameOver(EndOneThirdDead(_random)); }
_io.WriteLine(DeathsStarvation(_starvationDeaths));
}
var pollutionControl = _pollutionControlCost >= 25 ? _pollutionControlCost / 25 : 1;
_pollutionDeaths = (int)(_random.Next((int)_country.IndustryLand) / pollutionControl);
if (_pollutionDeaths > 0)
{
_io.WriteLine(DeathsPollution(_pollutionDeaths));
}
_deaths = (int)(_starvationDeaths + _pollutionDeaths);
if (_deaths > 0)
{
var funeralCosts = _deaths * 9;
_io.WriteLine(FuneralExpenses(funeralCosts));
if (!_country.TrySpend(funeralCosts, _landValue))
{
_io.WriteLine(InsufficientReserves);
}
_country.RemoveTheDead(_deaths);
}
return null;
}
private Result? EvaluateMigration()
{
if (_landSold > 0)
{
var newWorkers = (int)(_landSold + _random.NextFloat(10) - _random.NextFloat(20));
if (!_country.HasWorkers) { newWorkers += 20; }
_io.Write(WorkerMigration(newWorkers));
_country.AddWorkers(newWorkers);
}
_migration =
(int)(_citizenSupport / 10 + _pollutionControlCost / 25 - _country.IndustryLand / 50 - _pollutionDeaths / 2);
_io.WriteLine(Migration(_migration));
_country.Migration(_migration);
return null;
}
private Result? EvaluateAgriculture()
{
var ruinedCrops = (int)Math.Min(_country.IndustryLand * (_random.NextFloat() + 1.5f) / 2, _landPlanted);
var yield = (int)(_landPlanted - ruinedCrops);
var income = (int)(yield * _landValue / 2f);
_io.Write(LandPlanted(_landPlanted));
_io.Write(Harvest(yield, income, _country.IndustryLand > 0));
_country.SellCrops(income);
return null;
}
private Result? EvaluateTourism()
{
var reputationValue = (int)((_country.Countrymen - _migration) * 22 + _random.NextFloat(500));
var industryAdjustment = (int)(_country.IndustryLand * 15);
var tourismIncome = Math.Abs(reputationValue - industryAdjustment);
_io.WriteLine(TourismEarnings(tourismIncome));
if (industryAdjustment > 0 && tourismIncome < _country.PreviousTourismIncome)
{
_io.Write(TourismDecrease(_random));
}
_country.EntertainTourists(tourismIncome);
return null;
}
private Result? DetermineResult(float rallodsUnspent)
{
if (_deaths > 200) { return Result.GameOver(EndManyDead(_deaths, _random)); }
if (_country.Countrymen < 343) { return Result.GameOver(EndOneThirdDead(_random)); }
if (rallodsUnspent / 100 > 5 && _starvationDeaths >= 2) { return Result.GameOver(EndMoneyLeftOver()); }
if (_country.Workers > _country.Countrymen) { return Result.GameOver(EndForeignWorkers(_random)); }
return null;
}
}
+14
View File
@@ -48,3 +48,17 @@ http://www.vintage-basic.net/games.html
#### Porting Notes
(please note any difficulties or challenges in porting here)
Note: The original program has a bug. The instructions say that if both players
enter the same cell that the cell is set to 0 or empty. However, the original
Basic program tells the player "ILLEGAL COORDINATES" and makes another cell be entered,
giving a slightly unfair advantage to the 2nd player.
The Perl verson of the program fixes the bug and follows the instructions.
Note: The original code had "GOTO 800" but label 800 didn't exist; it should have gone to label 999.
The Basic program has been fixed.
Note: The Basic program is written to assume it's being played on a Teletype, i.e. output is printed
on paper. To play on a terminal the input must not be echoed, which can be a challenge to do portably
and without tying the solution to a specific OS. Some versions may tell you how to do this, others might not.
+2 -2
View File
@@ -60,8 +60,8 @@
571 IF M3=0 THEN B=1: GOTO 575
572 IF M2=0 THEN B=2: GOTO 575
573 GOTO 580
574 PRINT: PRINT "A DRAW":GOTO 800
575 PRINT: PRINT "PLAYER";B;"IS THE WINNER":GOTO 800
574 PRINT: PRINT "A DRAW":GOTO 999
575 PRINT: PRINT "PLAYER";B;"IS THE WINNER":GOTO 999
580 FOR B=1 TO 2: PRINT: PRINT: PRINT "PLAYER";B;: GOSUB 700
581 IF B=99 THEN 560
582 NEXT B
+18
View File
@@ -1,3 +1,21 @@
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
Conversion to [Perl](https://www.perl.org/)
Note: The original program has a bug (see the README in the above dir). This Perl version fixes it.
Note: For input, the X value is to the right while the Y value is down.
Therefore, the top right cell is "5,1", not "1,5".
The original program was made to be played on a Teletype, i.e. a printer on paper.
That allowed the program to "black out" the input line to hide a user's input from his/her
opponent, assuming the opponent was at least looking away. To do the equivalent on a
terminal would require a Perl module that isn't installed by default (i.e. it is not
part of CORE and would also require a C compiler to install), nor do I want to issue a
shell command to "stty" to hide the input because that would restrict the game to Linux/Unix.
This means it would have to be played on the honor system.
However, if you want to try it, install the module "Term::ReadKey" ("sudo cpan -i Term::ReadKey"
if on Linux/Unix and you have root access). If the code finds that module, it will automatically
use it and hide the input ... and restore echoing input again when the games ends. If the module
is not found, input will be visible.
+215
View File
@@ -0,0 +1,215 @@
#!/usr/bin/perl
# Life_For_Two program in Perl
# Required extensive restructuring to remove all of the GOTO's.
# Translated by Kevin Brannen (kbrannen)
use strict;
use warnings;
# try to load module to hide input, set RKey to true if found
my $Rkey = eval { require Term::ReadKey } // 0;
END { Term::ReadKey::ReadMode('normal') if ($Rkey); }
# globals
my @Board; # 2D board
my @X; # ?
my @Y; # ?
my $Player; # 1 or 2
my $M2 = 0; # ?
my $M3 = 0; # ?
# add 0 on front to make data 1 based
my @K = (0,3,102,103,120,130,121,112,111,12,21,30,1020,1030,1011,1021,1003,1002,1012);
my @A = (0,-1,0,1,0,0,-1,0,1,-1,-1,1,-1,-1,1,1,1);
print "\n";
print " " x 33, "LIFE2\n";
print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n";
print " " x 10, "U.B. LIFE GAME\n";
for my $j (1 .. 5) { for my $k (1 .. 5) { $Board[$j][$k] = 0; } }
for (1 .. 2)
{
$Player = $_; # if we make $Player the loop var, the global isn't set
my $p1 = ($Player == 2) ? 30 : 3;
print "\nPLAYER $Player - 3 LIVE PIECES.\n";
for (1 .. 3)
{
get_input();
$Board[$X[$Player]][$Y[$Player]] = $p1 if ($Player != 99);
}
}
print_board(); # print board after initial input
while (1)
{
print "\n";
calc_board(); # calc new positions
print_board(); # print current board after calc
if ($M2 == 0 && $M3 == 0)
{
print "\nA DRAW\n";
last;
}
if ($M3 == 0)
{
win(1);
last;
}
if ($M2 == 0)
{
win(2);
last;
}
for (1 .. 2)
{
$Player = $_; # if we make $Player the loop var, the global isn't set
print "\n\nPLAYER $Player ";
get_input();
last if ($Player == 99);
}
next if ($Player == 99);
$Board[$X[1]][$Y[1]] = 100;
$Board[$X[2]][$Y[2]] = 1000;
}
exit(0);
###########################################################
sub win
{
my $p = shift;
print "\nPLAYER $p IS THE WINNER\n";
}
sub calc_board
{
for my $j (1 .. 5)
{
for my $k (1 .. 5)
{
if ($Board[$j][$k] > 99)
{
$Player = $Board[$j][$k] > 999 ? 10 : 1;
for (my $c = 1 ; $c <= 15 ; $c += 2)
{
$Board[$j+$A[$c]][$k+$A[$c+1]] = ($Board[$j+$A[$c]][$k+$A[$c+1]] // 0) + $Player;
}
}
}
}
}
sub print_board
{
$M2 = 0;
$M3 = 0;
for my $j (0 .. 6)
{
print "\n";
for my $k (0 .. 6)
{
if ($j != 0 && $j != 6)
{
if ($k != 0 && $k != 6)
{
print_row($j, $k);
next;
}
if ($j == 6)
{
print "0\n";
return;
}
print " $j ";
}
else
{
if ($k == 6)
{
print " 0 ";
last;
}
print " $k ";
}
}
}
}
sub print_row
{
my ($j, $k) = @_;
if ($Board[$j][$k] >= 3)
{
my $c;
for $c (1 .. 18)
{
if ($Board[$j][$k] == $K[$c])
{
if ($c <= 9)
{
$Board[$j][$k] = 100;
$M2++;
print " * ";
}
else
{
$Board[$j][$k] = 1000;
$M3++;
print " # ";
}
return;
}
}
}
$Board[$j][$k] = 0;
print " ";
}
sub get_input
{
while (1)
{
print "X,Y\n";
my $ans;
if ($Rkey)
{
# code to hide input
Term::ReadKey::ReadMode('noecho');
$ans = Term::ReadKey::ReadLine(0);
Term::ReadKey::ReadMode('restore');
print "\n"; # do this since the one entered was hidden
}
else
{
# normal, input visible
chomp($ans = <>);
}
($Y[$Player], $X[$Player]) = split(/[,\s]+/, $ans, 2);
if ($X[$Player] > 5 || $X[$Player] < 1 || $Y[$Player] > 5 || $Y[$Player] < 1)
{
print "ILLEGAL COORDS. RETYPE\n";
next;
}
# this tells you the cell was already taken not zero it out, bug!
#if ($Board[$X[$Player]][$Y[$Player]] != 0)
#{
# print "ILLEGAL COORDS. RETYPE\n";
# next;
#}
last;
}
return if ($Player == 1 || $X[1] != $X[2] || $Y[1] != $Y[2]);
print "SAME COORD. SET TO 0\n";
$Board[$X[$Player]+1][$Y[$Player]+1] = 0;
$Player = 99;
}
+34
View File
@@ -0,0 +1,34 @@
namespace Queen;
internal class Computer
{
private static readonly HashSet<Position> _randomiseFrom = new() { 41, 44, 73, 75, 126, 127 };
private static readonly HashSet<Position> _desirable = new() { 73, 75, 126, 127, 158 };
private readonly IRandom _random;
public Computer(IRandom random)
{
_random = random;
}
public Position GetMove(Position from)
=> from + (_randomiseFrom.Contains(from) ? _random.NextMove() : FindMove(from));
private Move FindMove(Position from)
{
for (int i = 7; i > 0; i--)
{
if (IsOptimal(Move.Left, out var move)) { return move; }
if (IsOptimal(Move.Down, out move)) { return move; }
if (IsOptimal(Move.DownLeft, out move)) { return move; }
bool IsOptimal(Move direction, out Move move)
{
move = direction * i;
return _desirable.Contains(from + move);
}
}
return _random.NextMove();
}
}
+57
View File
@@ -0,0 +1,57 @@
namespace Queen;
internal class Game
{
private readonly IReadWrite _io;
private readonly IRandom _random;
private readonly Computer _computer;
public Game(IReadWrite io, IRandom random)
{
_io = io;
_random = random;
_computer = new Computer(random);
}
internal void PlaySeries()
{
_io.Write(Streams.Title);
if (_io.ReadYesNo(Prompts.Instructions)) { _io.Write(Streams.Instructions); }
while (true)
{
var result = PlayGame();
_io.Write(result switch
{
Result.HumanForfeits => Streams.Forfeit,
Result.HumanWins => Streams.Congratulations,
Result.ComputerWins => Streams.IWin,
_ => throw new InvalidOperationException($"Unexpected result {result}")
});
if (!_io.ReadYesNo(Prompts.Anyone)) { break; }
}
_io.Write(Streams.Thanks);
}
private Result PlayGame()
{
_io.Write(Streams.Board);
var humanPosition = _io.ReadPosition(Prompts.Start, p => p.IsStart, Streams.IllegalStart, repeatPrompt: true);
if (humanPosition.IsZero) { return Result.HumanForfeits; }
while (true)
{
var computerPosition = _computer.GetMove(humanPosition);
_io.Write(Strings.ComputerMove(computerPosition));
if (computerPosition.IsEnd) { return Result.ComputerWins; }
humanPosition = _io.ReadPosition(Prompts.Move, p => (p - computerPosition).IsValid, Streams.IllegalMove);
if (humanPosition.IsZero) { return Result.HumanForfeits; }
if (humanPosition.IsEnd) { return Result.HumanWins; }
}
}
private enum Result { ComputerWins, HumanWins, HumanForfeits };
}
+38
View File
@@ -0,0 +1,38 @@
namespace Queen;
internal static class IOExtensions
{
internal static bool ReadYesNo(this IReadWrite io, string prompt)
{
while (true)
{
var answer = io.ReadString(prompt).ToLower();
if (answer == "yes") { return true; }
if (answer == "no") { return false; }
io.Write(Streams.YesOrNo);
}
}
internal static Position ReadPosition(
this IReadWrite io,
string prompt,
Predicate<Position> isValid,
Stream error,
bool repeatPrompt = false)
{
while (true)
{
var response = io.ReadNumber(prompt);
var number = (int)response;
var position = new Position(number);
if (number == response && (position.IsZero || isValid(position)))
{
return position;
}
io.Write(error);
if (!repeatPrompt) { prompt = ""; }
}
}
}
+15
View File
@@ -0,0 +1,15 @@
namespace Queen;
internal record struct Move(int Diagonal, int Row)
{
public static readonly Move Left = new(1, 0);
public static readonly Move DownLeft = new(2, 1);
public static readonly Move Down = new(1, 1);
public bool IsValid => Diagonal > 0 && (IsLeft || IsDown || IsDownLeft);
private bool IsLeft => Row == 0;
private bool IsDown => Row == Diagonal;
private bool IsDownLeft => Row * 2 == Diagonal;
public static Move operator *(Move move, int scale) => new(move.Diagonal * scale, move.Row * scale);
}
+24
View File
@@ -0,0 +1,24 @@
namespace Queen;
internal record struct Position(int Diagonal, int Row)
{
public static readonly Position Zero = new(0);
public Position(int number)
: this(Diagonal: number / 10, Row: number % 10)
{
}
public bool IsZero => Row == 0 && Diagonal == 0;
public bool IsStart => Row == 1 || Row == Diagonal;
public bool IsEnd => Row == 8 && Diagonal == 15;
public override string ToString() => $"{Diagonal}{Row}";
public static implicit operator Position(int value) => new(value);
public static Position operator +(Position position, Move move)
=> new(Diagonal: position.Diagonal + move.Diagonal, Row: position.Row + move.Row);
public static Move operator -(Position to, Position from)
=> new(Diagonal: to.Diagonal - from.Diagonal, Row: to.Row - from.Row);
}
+7
View File
@@ -0,0 +1,7 @@
global using Games.Common.IO;
global using Games.Common.Randomness;
global using static Queen.Resources.Resource;
using Queen;
new Game(new ConsoleIO(), new RandomNumberGenerator()).PlaySeries();
+8
View File
@@ -6,4 +6,12 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Resources/*.txt" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
</ItemGroup>
</Project>
+12
View File
@@ -0,0 +1,12 @@
namespace Queen;
internal static class RandomExtensions
{
internal static Move NextMove(this IRandom random)
=> random.NextFloat() switch
{
> 0.6F => Move.Down,
> 0.3F => Move.DownLeft,
_ => Move.Left
};
}
@@ -0,0 +1 @@
Anyone else care to try
+27
View File
@@ -0,0 +1,27 @@
81 71 61 51 41 31 21 11
92 82 72 62 52 42 32 22
103 93 83 73 63 53 43 33
114 104 94 84 74 64 54 44
125 115 105 95 85 75 65 55
136 126 116 106 96 86 76 66
147 137 127 117 107 97 87 77
158 148 138 128 118 108 98 88
@@ -0,0 +1 @@
Computer moves to square {0}
@@ -0,0 +1,7 @@
C O N G R A T U L A T I O N S . . .
You have won--very well played.
It looks like I have met my match.
Thanks for playing--I can't win all the time.
+3
View File
@@ -0,0 +1,3 @@
It looks like I have won by forfeit.
+4
View File
@@ -0,0 +1,4 @@
Nice try, but it looks like I have won.
Thanks for playing.
@@ -0,0 +1,2 @@
Y O U C H E A T . . . Try again
@@ -0,0 +1,3 @@
Please read the instructions again.
You have begun illegally.
@@ -0,0 +1,15 @@
We are going to play a game based on one of the chess
moves. Our queen will be able to move only to the left,
down, or diagonally down and to the left.
The object of the game is to place the queen in the lower
left hand square by alternating moves between you and the
computer. The first one to place the queen there wins.
You go first and place the queen in any one of the squares
on the top row or right hand column.
That will be your first move.
We alternate moves.
You may forfeit by typing '0' as your move.
Be sure to press the return key after each response.
@@ -0,0 +1 @@
Do you want instructions
+1
View File
@@ -0,0 +1 @@
What is your move
+47
View File
@@ -0,0 +1,47 @@
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Queen.Resources;
internal static class Resource
{
internal static class Streams
{
public static Stream Title => GetStream();
public static Stream Instructions => GetStream();
public static Stream YesOrNo => GetStream();
public static Stream Board => GetStream();
public static Stream IllegalStart => GetStream();
public static Stream IllegalMove => GetStream();
public static Stream Forfeit => GetStream();
public static Stream IWin => GetStream();
public static Stream Congratulations => GetStream();
public static Stream Thanks => GetStream();
}
internal static class Prompts
{
public static string Instructions => GetPrompt();
public static string Start => GetPrompt();
public static string Move => GetPrompt();
public static string Anyone => GetPrompt();
}
internal static class Strings
{
public static string ComputerMove(Position position) => string.Format(GetString(), position);
}
private static string GetPrompt([CallerMemberName] string? name = null) => GetString($"{name}Prompt");
private static string GetString([CallerMemberName] string? name = null)
{
using var stream = GetStream(name);
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
}
private static Stream GetStream([CallerMemberName] string? name = null) =>
Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(Resource).Namespace}.{name}.txt")
?? throw new Exception($"Could not find embedded resource stream '{name}'.");
}
@@ -0,0 +1 @@
Where would you like to start

Some files were not shown because too many files have changed in this diff Show More