mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-01-05 17:48:52 -08:00
Merge branch 'coding-horror:main' into main
This commit is contained in:
73
00_Utilities/find-unimplemented.js
Normal file
73
00_Utilities/find-unimplemented.js
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* Program to show unimplemented games by language, optionally filtered by
|
||||||
|
* language
|
||||||
|
*
|
||||||
|
* Usage: node find-unimplemented.js [[[lang1] lang2] ...]
|
||||||
|
*
|
||||||
|
* Adapted from find-missing-implementtion.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require("fs");
|
||||||
|
const glob = require("glob");
|
||||||
|
|
||||||
|
// relative path to the repository root
|
||||||
|
const ROOT_PATH = "../.";
|
||||||
|
|
||||||
|
let languages = [
|
||||||
|
{ name: "csharp", extension: "cs" },
|
||||||
|
{ name: "java", extension: "java" },
|
||||||
|
{ name: "javascript", extension: "html" },
|
||||||
|
{ name: "pascal", extension: "pas" },
|
||||||
|
{ name: "perl", extension: "pl" },
|
||||||
|
{ name: "python", extension: "py" },
|
||||||
|
{ name: "ruby", extension: "rb" },
|
||||||
|
{ name: "vbnet", extension: "vb" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const getFilesRecursive = async (path, extension) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
glob(`${path}/**/*.${extension}`, (err, matches) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
resolve(matches);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPuzzleFolders = () => {
|
||||||
|
return fs
|
||||||
|
.readdirSync(ROOT_PATH, { withFileTypes: true })
|
||||||
|
.filter((dirEntry) => dirEntry.isDirectory())
|
||||||
|
.filter(
|
||||||
|
(dirEntry) =>
|
||||||
|
![".git", "node_modules", "00_Utilities", "buildJvm"].includes(dirEntry.name)
|
||||||
|
)
|
||||||
|
.map((dirEntry) => dirEntry.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const result = {};
|
||||||
|
if (process.argv.length > 2) {
|
||||||
|
languages = languages.filter((language) => process.argv.slice(2).includes(language.name));
|
||||||
|
}
|
||||||
|
for (const { name: language } of languages) {
|
||||||
|
result[language] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const puzzleFolders = getPuzzleFolders();
|
||||||
|
for (const puzzleFolder of puzzleFolders) {
|
||||||
|
for (const { name: language, extension } of languages) {
|
||||||
|
const files = await getFilesRecursive(
|
||||||
|
`${ROOT_PATH}/${puzzleFolder}/${language}`, extension
|
||||||
|
);
|
||||||
|
if (files.length === 0) {
|
||||||
|
result[language].push(puzzleFolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('Unimplementation by language:')
|
||||||
|
console.dir(result);
|
||||||
|
})();
|
||||||
|
|
||||||
|
return;
|
||||||
@@ -10,6 +10,8 @@ import java.util.stream.Collectors;
|
|||||||
* Converted from BASIC to Java by Aldrin Misquitta (@aldrinm)
|
* Converted from BASIC to Java by Aldrin Misquitta (@aldrinm)
|
||||||
* The original BASIC program uses an array to maintain the questions and answers and to decide which question to
|
* The original BASIC program uses an array to maintain the questions and answers and to decide which question to
|
||||||
* ask next. Updated this Java implementation to use a tree instead of the earlier faulty one based on a list (thanks @patimen).
|
* ask next. Updated this Java implementation to use a tree instead of the earlier faulty one based on a list (thanks @patimen).
|
||||||
|
*
|
||||||
|
* Bonus option: TREE --> prints the game decision data as a tree to visualize/debug the state of the game
|
||||||
*/
|
*/
|
||||||
public class Animal {
|
public class Animal {
|
||||||
|
|
||||||
|
|||||||
92
06_Banner/ruby/banner.rb
Executable file
92
06_Banner/ruby/banner.rb
Executable file
@@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
# Banner
|
||||||
|
# reinterpreted from BASIC by stephan.com
|
||||||
|
|
||||||
|
# this implementation diverges from the original in some notable
|
||||||
|
# ways, but maintains the same font definition as before as well
|
||||||
|
# as the same somewhat bizarre way of interpreting it. It would
|
||||||
|
# be more efficient to redesign the font to allow `"%09b" % row`
|
||||||
|
# and then some substitutions.
|
||||||
|
|
||||||
|
FONT = {
|
||||||
|
' ' => [0, 0, 0, 0, 0, 0, 0].freeze,
|
||||||
|
'!' => [1, 1, 1, 384, 1, 1, 1].freeze,
|
||||||
|
'*' => [69, 41, 17, 512, 17, 41, 69].freeze,
|
||||||
|
'.' => [1, 1, 129, 449, 129, 1, 1].freeze,
|
||||||
|
'0' => [57, 69, 131, 258, 131, 69, 57].freeze,
|
||||||
|
'1' => [0, 0, 261, 259, 512, 257, 257].freeze,
|
||||||
|
'2' => [261, 387, 322, 290, 274, 267, 261].freeze,
|
||||||
|
'3' => [66, 130, 258, 274, 266, 150, 100].freeze,
|
||||||
|
'4' => [33, 49, 41, 37, 35, 512, 33].freeze,
|
||||||
|
'5' => [160, 274, 274, 274, 274, 274, 226].freeze,
|
||||||
|
'6' => [194, 291, 293, 297, 305, 289, 193].freeze,
|
||||||
|
'7' => [258, 130, 66, 34, 18, 10, 8].freeze,
|
||||||
|
'8' => [69, 171, 274, 274, 274, 171, 69].freeze,
|
||||||
|
'9' => [263, 138, 74, 42, 26, 10, 7].freeze,
|
||||||
|
'=' => [41, 41, 41, 41, 41, 41, 41].freeze,
|
||||||
|
'?' => [5, 3, 2, 354, 18, 11, 5].freeze,
|
||||||
|
'a' => [505, 37, 35, 34, 35, 37, 505].freeze,
|
||||||
|
'b' => [512, 274, 274, 274, 274, 274, 239].freeze,
|
||||||
|
'c' => [125, 131, 258, 258, 258, 131, 69].freeze,
|
||||||
|
'd' => [512, 258, 258, 258, 258, 131, 125].freeze,
|
||||||
|
'e' => [512, 274, 274, 274, 274, 258, 258].freeze,
|
||||||
|
'f' => [512, 18, 18, 18, 18, 2, 2].freeze,
|
||||||
|
'g' => [125, 131, 258, 258, 290, 163, 101].freeze,
|
||||||
|
'h' => [512, 17, 17, 17, 17, 17, 512].freeze,
|
||||||
|
'i' => [258, 258, 258, 512, 258, 258, 258].freeze,
|
||||||
|
'j' => [65, 129, 257, 257, 257, 129, 128].freeze,
|
||||||
|
'k' => [512, 17, 17, 41, 69, 131, 258].freeze,
|
||||||
|
'l' => [512, 257, 257, 257, 257, 257, 257].freeze,
|
||||||
|
'm' => [512, 7, 13, 25, 13, 7, 512].freeze,
|
||||||
|
'n' => [512, 7, 9, 17, 33, 193, 512].freeze,
|
||||||
|
'o' => [125, 131, 258, 258, 258, 131, 125].freeze,
|
||||||
|
'p' => [512, 18, 18, 18, 18, 18, 15].freeze,
|
||||||
|
'q' => [125, 131, 258, 258, 322, 131, 381].freeze,
|
||||||
|
'r' => [512, 18, 18, 50, 82, 146, 271].freeze,
|
||||||
|
's' => [69, 139, 274, 274, 274, 163, 69].freeze,
|
||||||
|
't' => [2, 2, 2, 512, 2, 2, 2].freeze,
|
||||||
|
'u' => [128, 129, 257, 257, 257, 129, 128].freeze,
|
||||||
|
'v' => [64, 65, 129, 257, 129, 65, 64].freeze,
|
||||||
|
'w' => [256, 257, 129, 65, 129, 257, 256].freeze,
|
||||||
|
'x' => [388, 69, 41, 17, 41, 69, 388].freeze,
|
||||||
|
'y' => [8, 9, 17, 481, 17, 9, 8].freeze,
|
||||||
|
'z' => [386, 322, 290, 274, 266, 262, 260].freeze
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
puts 'horizontal'
|
||||||
|
x = gets.strip.to_i
|
||||||
|
puts 'vertical'
|
||||||
|
y = gets.strip.to_i
|
||||||
|
puts 'centered'
|
||||||
|
centered = gets.strip.downcase.chars.first == 'y'
|
||||||
|
puts 'character ("all" for character being printed)'
|
||||||
|
fill = gets.strip.downcase
|
||||||
|
puts 'statement'
|
||||||
|
statement = gets.strip.downcase
|
||||||
|
|
||||||
|
all = (fill.downcase == 'all')
|
||||||
|
lenxs = all ? 1 : fill.length
|
||||||
|
start = 1
|
||||||
|
start += (63 - 4.5 * y) / lenxs if centered
|
||||||
|
|
||||||
|
statement.each_char do |char|
|
||||||
|
next puts "\n" * 7 * x if char == ' '
|
||||||
|
|
||||||
|
xs = all ? char : fill
|
||||||
|
FONT[char].each do |su|
|
||||||
|
print ' ' * start
|
||||||
|
8.downto(0) do |k|
|
||||||
|
if (1 << k) < su
|
||||||
|
print xs * y
|
||||||
|
su -= (1 << k)
|
||||||
|
else
|
||||||
|
print ' ' * (y * lenxs)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
puts
|
||||||
|
end
|
||||||
|
|
||||||
|
(2 * x).times { puts }
|
||||||
|
end
|
||||||
|
75.times { puts }
|
||||||
198
14_Bowling/csharp/Bowling.cs
Normal file
198
14_Bowling/csharp/Bowling.cs
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Bowling
|
||||||
|
{
|
||||||
|
public class Bowling
|
||||||
|
{
|
||||||
|
private readonly Pins pins = new();
|
||||||
|
|
||||||
|
private int players;
|
||||||
|
|
||||||
|
public void Play()
|
||||||
|
{
|
||||||
|
ShowBanner();
|
||||||
|
MaybeShowInstructions();
|
||||||
|
Setup();
|
||||||
|
GameLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ShowBanner()
|
||||||
|
{
|
||||||
|
Utility.PrintString(34, "BOWL");
|
||||||
|
Utility.PrintString(15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
|
||||||
|
Utility.PrintString();
|
||||||
|
Utility.PrintString();
|
||||||
|
Utility.PrintString();
|
||||||
|
Utility.PrintString("WELCOME TO THE ALLEY");
|
||||||
|
Utility.PrintString("BRING YOUR FRIENDS");
|
||||||
|
Utility.PrintString("OKAY LET'S FIRST GET ACQUAINTED");
|
||||||
|
Utility.PrintString();
|
||||||
|
}
|
||||||
|
private static void MaybeShowInstructions()
|
||||||
|
{
|
||||||
|
Utility.PrintString("THE INSTRUCTIONS (Y/N)");
|
||||||
|
if (Utility.InputString() == "N") return;
|
||||||
|
Utility.PrintString("THE GAME OF BOWLING TAKES MIND AND SKILL.DURING THE GAME");
|
||||||
|
Utility.PrintString("THE COMPUTER WILL KEEP SCORE.YOU MAY COMPETE WITH");
|
||||||
|
Utility.PrintString("OTHER PLAYERS[UP TO FOUR].YOU WILL BE PLAYING TEN FRAMES");
|
||||||
|
Utility.PrintString("ON THE PIN DIAGRAM 'O' MEANS THE PIN IS DOWN...'+' MEANS THE");
|
||||||
|
Utility.PrintString("PIN IS STANDING.AFTER THE GAME THE COMPUTER WILL SHOW YOUR");
|
||||||
|
Utility.PrintString("SCORES .");
|
||||||
|
}
|
||||||
|
private void Setup()
|
||||||
|
{
|
||||||
|
Utility.PrintString("FIRST OF ALL...HOW MANY ARE PLAYING", false);
|
||||||
|
var input = Utility.InputInt();
|
||||||
|
players = input < 1 ? 1 : input;
|
||||||
|
Utility.PrintString();
|
||||||
|
Utility.PrintString("VERY GOOD...");
|
||||||
|
}
|
||||||
|
private void GameLoop()
|
||||||
|
{
|
||||||
|
GameResults[] gameResults = InitGameResults();
|
||||||
|
var done = false;
|
||||||
|
while (!done)
|
||||||
|
{
|
||||||
|
ResetGameResults(gameResults);
|
||||||
|
for (int frame = 0; frame < GameResults.FramesPerGame; ++frame)
|
||||||
|
{
|
||||||
|
for (int player = 0; player < players; ++player)
|
||||||
|
{
|
||||||
|
pins.Reset();
|
||||||
|
int pinsDownThisFrame = pins.GetPinsDown();
|
||||||
|
|
||||||
|
int ball = 1;
|
||||||
|
while (ball == 1 || ball == 2) // One or two rolls
|
||||||
|
{
|
||||||
|
Utility.PrintString("TYPE ROLL TO GET THE BALL GOING.");
|
||||||
|
_ = Utility.InputString();
|
||||||
|
|
||||||
|
int pinsDownAfterRoll = pins.Roll();
|
||||||
|
ShowPins(player, frame, ball);
|
||||||
|
|
||||||
|
if (pinsDownAfterRoll == pinsDownThisFrame)
|
||||||
|
{
|
||||||
|
Utility.PrintString("GUTTER!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ball == 1)
|
||||||
|
{
|
||||||
|
// Store current pin count
|
||||||
|
gameResults[player].Results[frame].PinsBall1 = pinsDownAfterRoll;
|
||||||
|
|
||||||
|
// Special handling for strike
|
||||||
|
if (pinsDownAfterRoll == Pins.TotalPinCount)
|
||||||
|
{
|
||||||
|
Utility.PrintString("STRIKE!!!!!\a\a\a\a");
|
||||||
|
// No second roll
|
||||||
|
ball = 0;
|
||||||
|
gameResults[player].Results[frame].PinsBall2 = pinsDownAfterRoll;
|
||||||
|
gameResults[player].Results[frame].Score = FrameResult.Points.Strike;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ball = 2; // Roll again
|
||||||
|
Utility.PrintString("ROLL YOUR SECOND BALL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ball == 2)
|
||||||
|
{
|
||||||
|
// Store current pin count
|
||||||
|
gameResults[player].Results[frame].PinsBall2 = pinsDownAfterRoll;
|
||||||
|
ball = 0;
|
||||||
|
|
||||||
|
// Determine the score for the frame
|
||||||
|
if (pinsDownAfterRoll == Pins.TotalPinCount)
|
||||||
|
{
|
||||||
|
Utility.PrintString("SPARE!!!!");
|
||||||
|
gameResults[player].Results[frame].Score = FrameResult.Points.Spare;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Utility.PrintString("ERROR!!!");
|
||||||
|
gameResults[player].Results[frame].Score = FrameResult.Points.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Utility.PrintString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ShowGameResults(gameResults);
|
||||||
|
Utility.PrintString("DO YOU WANT ANOTHER GAME");
|
||||||
|
var a = Utility.InputString();
|
||||||
|
done = a.Length == 0 || a[0] != 'Y';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private GameResults[] InitGameResults()
|
||||||
|
{
|
||||||
|
var gameResults = new GameResults[players];
|
||||||
|
for (int i = 0; i < gameResults.Length; i++)
|
||||||
|
{
|
||||||
|
gameResults[i] = new GameResults();
|
||||||
|
}
|
||||||
|
return gameResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowPins(int player, int frame, int ball)
|
||||||
|
{
|
||||||
|
Utility.PrintString($"FRAME: {frame + 1} PLAYER: {player + 1} BALL: {ball}");
|
||||||
|
var breakPins = new bool[] { true, false, false, false, true, false, false, true, false, true };
|
||||||
|
var indent = 0;
|
||||||
|
for (int pin = 0; pin < Pins.TotalPinCount; ++pin)
|
||||||
|
{
|
||||||
|
if (breakPins[pin])
|
||||||
|
{
|
||||||
|
Utility.PrintString(); // End row
|
||||||
|
Utility.PrintString(indent++, false); // Indent next row
|
||||||
|
}
|
||||||
|
var s = pins[pin] == Pins.State.Down ? "+ " : "o ";
|
||||||
|
Utility.PrintString(s, false);
|
||||||
|
}
|
||||||
|
Utility.PrintString();
|
||||||
|
Utility.PrintString();
|
||||||
|
}
|
||||||
|
private void ResetGameResults(GameResults[] gameResults)
|
||||||
|
{
|
||||||
|
foreach (var gameResult in gameResults)
|
||||||
|
{
|
||||||
|
foreach (var frameResult in gameResult.Results)
|
||||||
|
{
|
||||||
|
frameResult.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void ShowGameResults(GameResults[] gameResults)
|
||||||
|
{
|
||||||
|
Utility.PrintString("FRAMES");
|
||||||
|
for (int i = 0; i < GameResults.FramesPerGame; ++i)
|
||||||
|
{
|
||||||
|
Utility.PrintString(Utility.PadInt(i, 3), false);
|
||||||
|
}
|
||||||
|
Utility.PrintString();
|
||||||
|
foreach (var gameResult in gameResults)
|
||||||
|
{
|
||||||
|
foreach (var frameResult in gameResult.Results)
|
||||||
|
{
|
||||||
|
Utility.PrintString(Utility.PadInt(frameResult.PinsBall1, 3), false);
|
||||||
|
}
|
||||||
|
Utility.PrintString();
|
||||||
|
foreach (var frameResult in gameResult.Results)
|
||||||
|
{
|
||||||
|
Utility.PrintString(Utility.PadInt(frameResult.PinsBall2, 3), false);
|
||||||
|
}
|
||||||
|
Utility.PrintString();
|
||||||
|
foreach (var frameResult in gameResult.Results)
|
||||||
|
{
|
||||||
|
Utility.PrintString(Utility.PadInt((int)frameResult.Score, 3), false);
|
||||||
|
}
|
||||||
|
Utility.PrintString();
|
||||||
|
Utility.PrintString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
14_Bowling/csharp/FrameResult.cs
Normal file
23
14_Bowling/csharp/FrameResult.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Bowling
|
||||||
|
{
|
||||||
|
public class FrameResult
|
||||||
|
{
|
||||||
|
public enum Points { None, Error, Spare, Strike };
|
||||||
|
|
||||||
|
public int PinsBall1 { get; set; }
|
||||||
|
public int PinsBall2 { get; set; }
|
||||||
|
public Points Score { get; set; }
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
PinsBall1 = PinsBall2 = 0;
|
||||||
|
Score = Points.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
14_Bowling/csharp/GameResults.cs
Normal file
23
14_Bowling/csharp/GameResults.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Bowling
|
||||||
|
{
|
||||||
|
public class GameResults
|
||||||
|
{
|
||||||
|
public static readonly int FramesPerGame = 10;
|
||||||
|
public FrameResult[] Results { get; set; }
|
||||||
|
|
||||||
|
public GameResults()
|
||||||
|
{
|
||||||
|
Results = new FrameResult[FramesPerGame];
|
||||||
|
for (int i = 0; i < FramesPerGame; ++i)
|
||||||
|
{
|
||||||
|
Results[i] = new FrameResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
14_Bowling/csharp/Pins.cs
Normal file
56
14_Bowling/csharp/Pins.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Bowling
|
||||||
|
{
|
||||||
|
public class Pins
|
||||||
|
{
|
||||||
|
public enum State { Up, Down };
|
||||||
|
public static readonly int TotalPinCount = 10;
|
||||||
|
private readonly Random random = new();
|
||||||
|
|
||||||
|
private State[] PinSet { get; set; }
|
||||||
|
|
||||||
|
public Pins()
|
||||||
|
{
|
||||||
|
PinSet = new State[TotalPinCount];
|
||||||
|
}
|
||||||
|
public State this[int i]
|
||||||
|
{
|
||||||
|
get { return PinSet[i]; }
|
||||||
|
set { PinSet[i] = value; }
|
||||||
|
}
|
||||||
|
public int Roll()
|
||||||
|
{
|
||||||
|
// REM ARK BALL GENERATOR USING MOD '15' SYSTEM
|
||||||
|
for (int i = 0; i < 20; ++i)
|
||||||
|
{
|
||||||
|
var x = random.Next(100) + 1;
|
||||||
|
int j;
|
||||||
|
for (j = 1; j <= 10; ++j)
|
||||||
|
{
|
||||||
|
if (x < 15 * j)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
var pindex = 15 * j - x;
|
||||||
|
if (pindex > 0 && pindex <= TotalPinCount)
|
||||||
|
PinSet[--pindex] = State.Down;
|
||||||
|
}
|
||||||
|
return GetPinsDown();
|
||||||
|
}
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < PinSet.Length; ++i)
|
||||||
|
{
|
||||||
|
PinSet[i] = State.Up;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int GetPinsDown()
|
||||||
|
{
|
||||||
|
return PinSet.Count(p => p == State.Down);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
14_Bowling/csharp/Program.cs
Normal file
16
14_Bowling/csharp/Program.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Bowling
|
||||||
|
{
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static void Main()
|
||||||
|
{
|
||||||
|
new Bowling().Play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
54
14_Bowling/csharp/Utility.cs
Normal file
54
14_Bowling/csharp/Utility.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Bowling
|
||||||
|
{
|
||||||
|
internal static class Utility
|
||||||
|
{
|
||||||
|
public static string PadInt(int value, int width)
|
||||||
|
{
|
||||||
|
return value.ToString().PadLeft(width);
|
||||||
|
}
|
||||||
|
public static int InputInt()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (int.TryParse(InputString(), out int i))
|
||||||
|
return i;
|
||||||
|
else
|
||||||
|
PrintString("!NUMBER EXPECTED - RETRY INPUT LINE");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static string InputString()
|
||||||
|
{
|
||||||
|
PrintString("? ", false);
|
||||||
|
var input = Console.ReadLine();
|
||||||
|
return input == null ? string.Empty : input.ToUpper();
|
||||||
|
}
|
||||||
|
public static void PrintInt(int value, bool newLine = false)
|
||||||
|
{
|
||||||
|
PrintString($"{value} ", newLine);
|
||||||
|
}
|
||||||
|
public static void PrintString(bool newLine = true)
|
||||||
|
{
|
||||||
|
PrintString(0, string.Empty);
|
||||||
|
}
|
||||||
|
public static void PrintString(int tab, bool newLine = true)
|
||||||
|
{
|
||||||
|
PrintString(tab, string.Empty, newLine);
|
||||||
|
}
|
||||||
|
public static void PrintString(string value, bool newLine = true)
|
||||||
|
{
|
||||||
|
PrintString(0, value, newLine);
|
||||||
|
}
|
||||||
|
public static void PrintString(int tab, string value, bool newLine = true)
|
||||||
|
{
|
||||||
|
Console.Write(new String(' ', tab));
|
||||||
|
Console.Write(value);
|
||||||
|
if (newLine) Console.WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
19_Bunny/csharp/BasicData.cs
Normal file
25
19_Bunny/csharp/BasicData.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Bunny
|
||||||
|
{
|
||||||
|
internal class BasicData
|
||||||
|
{
|
||||||
|
private readonly int[] data;
|
||||||
|
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
public BasicData(int[] data)
|
||||||
|
{
|
||||||
|
this.data = data;
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
public int Read()
|
||||||
|
{
|
||||||
|
return data[index++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
87
19_Bunny/csharp/Bunny.cs
Normal file
87
19_Bunny/csharp/Bunny.cs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
namespace Bunny
|
||||||
|
{
|
||||||
|
internal class Bunny
|
||||||
|
{
|
||||||
|
private const int asciiBase = 64;
|
||||||
|
private readonly int[] bunnyData = {
|
||||||
|
2,21,14,14,25,
|
||||||
|
1,2,-1,0,2,45,50,-1,0,5,43,52,-1,0,7,41,52,-1,
|
||||||
|
1,9,37,50,-1,2,11,36,50,-1,3,13,34,49,-1,4,14,32,48,-1,
|
||||||
|
5,15,31,47,-1,6,16,30,45,-1,7,17,29,44,-1,8,19,28,43,-1,
|
||||||
|
9,20,27,41,-1,10,21,26,40,-1,11,22,25,38,-1,12,22,24,36,-1,
|
||||||
|
13,34,-1,14,33,-1,15,31,-1,17,29,-1,18,27,-1,
|
||||||
|
19,26,-1,16,28,-1,13,30,-1,11,31,-1,10,32,-1,
|
||||||
|
8,33,-1,7,34,-1,6,13,16,34,-1,5,12,16,35,-1,
|
||||||
|
4,12,16,35,-1,3,12,15,35,-1,2,35,-1,1,35,-1,
|
||||||
|
2,34,-1,3,34,-1,4,33,-1,6,33,-1,10,32,34,34,-1,
|
||||||
|
14,17,19,25,28,31,35,35,-1,15,19,23,30,36,36,-1,
|
||||||
|
14,18,21,21,24,30,37,37,-1,13,18,23,29,33,38,-1,
|
||||||
|
12,29,31,33,-1,11,13,17,17,19,19,22,22,24,31,-1,
|
||||||
|
10,11,17,18,22,22,24,24,29,29,-1,
|
||||||
|
22,23,26,29,-1,27,29,-1,28,29,-1,4096
|
||||||
|
};
|
||||||
|
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
PrintString(33, "BUNNY");
|
||||||
|
PrintString(15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
|
||||||
|
PrintLines(3);
|
||||||
|
|
||||||
|
// Set up a BASIC-ish data object
|
||||||
|
BasicData data = new (bunnyData);
|
||||||
|
|
||||||
|
// Get the first five data values into an array.
|
||||||
|
// These are the characters we are going to print.
|
||||||
|
// Unlike the original program, we are only converting
|
||||||
|
// them to ASCII once.
|
||||||
|
var a = new char[5];
|
||||||
|
for (var i = 0; i < 5; ++i)
|
||||||
|
{
|
||||||
|
a[i] = (char)(asciiBase + data.Read());
|
||||||
|
}
|
||||||
|
PrintLines(6);
|
||||||
|
|
||||||
|
PrintLines(1);
|
||||||
|
var col = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var x = data.Read();
|
||||||
|
if (x < 0) // Start a new line
|
||||||
|
{
|
||||||
|
PrintLines(1);
|
||||||
|
col = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (x > 128) break; // End processing
|
||||||
|
col += PrintSpaces(x - col); // Move to TAB position x (sort of)
|
||||||
|
var y = data.Read(); // Read the next value
|
||||||
|
for (var i = x; i <= y; ++i)
|
||||||
|
{
|
||||||
|
// var j = i - 5 * (i / 5); // BASIC didn't have a modulus operator
|
||||||
|
Console.Write(a[i % 5]);
|
||||||
|
// Console.Write(a[col % 5]); // This works, too
|
||||||
|
++col;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PrintLines(6);
|
||||||
|
}
|
||||||
|
private static void PrintLines(int count)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < count; ++i)
|
||||||
|
Console.WriteLine();
|
||||||
|
}
|
||||||
|
private static int PrintSpaces(int count)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < count; ++i)
|
||||||
|
Console.Write(' ');
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
public static void PrintString(int tab, string value, bool newLine = true)
|
||||||
|
{
|
||||||
|
PrintSpaces(tab);
|
||||||
|
Console.Write(value);
|
||||||
|
if (newLine) Console.WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
16
19_Bunny/csharp/Program.cs
Normal file
16
19_Bunny/csharp/Program.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Bunny
|
||||||
|
{
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static void Main()
|
||||||
|
{
|
||||||
|
new Bunny().Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
52_Kinema/ruby/kinema.rb
Normal file
45
52_Kinema/ruby/kinema.rb
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
# Kinema
|
||||||
|
# reinterpreted from BASIC by stephan.com
|
||||||
|
|
||||||
|
EPSILON = 0.15
|
||||||
|
|
||||||
|
def close?(guess, answer)
|
||||||
|
(guess-answer).abs < answer * EPSILON
|
||||||
|
end
|
||||||
|
|
||||||
|
def ask(text, answer)
|
||||||
|
puts text
|
||||||
|
guess = gets.strip.to_f
|
||||||
|
if close?(guess, answer)
|
||||||
|
puts 'Close enough'
|
||||||
|
@score += 1
|
||||||
|
else
|
||||||
|
puts 'Not even close....'
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "Correct answer is #{answer}"
|
||||||
|
end
|
||||||
|
|
||||||
|
puts 'Kinema'.center(80)
|
||||||
|
puts 'Adapted by stephan.com'.center(80)
|
||||||
|
puts; puts; puts;
|
||||||
|
|
||||||
|
loop do
|
||||||
|
puts; puts
|
||||||
|
@score = 0
|
||||||
|
v = 5 + rand(35)
|
||||||
|
|
||||||
|
puts "A ball is thrown upwards at #{v} meters per second"
|
||||||
|
|
||||||
|
ask 'How high will it go? (in meters)', 0.05 * v * v
|
||||||
|
ask 'How long until it returns? (in seconds)', v/5.0
|
||||||
|
|
||||||
|
t = 1 + rand(2*v)/10.0
|
||||||
|
ask "What will its velocity be after #{t} seconds?", v - 10 * t
|
||||||
|
puts
|
||||||
|
print "#{@score} right out of 3."
|
||||||
|
print " not bad" if @score > 1
|
||||||
|
puts
|
||||||
|
end
|
||||||
45
54_Letter/ruby/letter.rb
Normal file
45
54_Letter/ruby/letter.rb
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
# Kinema
|
||||||
|
# reinterpreted from BASIC by stephan.com
|
||||||
|
|
||||||
|
puts 'Letter'.center(80)
|
||||||
|
puts 'Adapted by stephan.com'.center(80)
|
||||||
|
puts "\n\n\n"
|
||||||
|
|
||||||
|
puts "Letter guessing game\n\n"
|
||||||
|
|
||||||
|
puts "I'll think of a letter of the alphabet, A to Z."
|
||||||
|
puts "Try to guess my letter and I'll give you clues"
|
||||||
|
puts "as to how close you're getting to my letter."
|
||||||
|
|
||||||
|
def win(turns)
|
||||||
|
puts "\nyou got it in #{turns} guesses!!"
|
||||||
|
return puts "but it shouldn't take more than 5 guesses!" if turns > 5
|
||||||
|
|
||||||
|
puts "good job !!!!!\a\a\a"
|
||||||
|
end
|
||||||
|
|
||||||
|
def play
|
||||||
|
letter = ('A'..'Z').to_a.sample
|
||||||
|
guess = nil
|
||||||
|
turn = 0
|
||||||
|
|
||||||
|
puts "\nO.K., I have a letter. Start guessing."
|
||||||
|
|
||||||
|
until guess == letter
|
||||||
|
puts "\nWhat is your guess?"
|
||||||
|
|
||||||
|
guess = gets.strip.chars.first.upcase
|
||||||
|
turn += 1
|
||||||
|
|
||||||
|
puts 'Too low. Try a higher letter.' if guess < letter
|
||||||
|
puts 'Too high. Try a lower letter.' if guess > letter
|
||||||
|
end
|
||||||
|
win(turn)
|
||||||
|
end
|
||||||
|
|
||||||
|
loop do
|
||||||
|
play
|
||||||
|
puts "\nlet's play again....."
|
||||||
|
end
|
||||||
43
58_Love/ruby/love.rb
Normal file
43
58_Love/ruby/love.rb
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
data = [60, 1, 12, 26, 9, 12, 3, 8, 24, 17, 8, 4, 6, 23, 21, 6, 4, 6, 22, 12, 5, 6, 5,
|
||||||
|
4, 6, 21, 11, 8, 6, 4, 4, 6, 21, 10, 10, 5, 4, 4, 6, 21, 9, 11, 5, 4, 4, 6, 21,
|
||||||
|
8, 11, 6, 4, 4, 6, 21, 7, 11, 7, 4, 4, 6, 21, 6, 11, 8, 4, 4, 6, 19, 1, 1, 5,
|
||||||
|
11, 9, 4, 4, 6, 19, 1, 1, 5, 10, 10, 4, 4, 6, 18, 2, 1, 6, 8, 11, 4, 4, 6, 17,
|
||||||
|
3, 1, 7, 5, 13, 4, 4, 6, 15, 5, 2, 23, 5, 1, 29, 5, 17, 8, 1, 29, 9, 9, 12, 1,
|
||||||
|
13, 5, 40, 1, 1, 13, 5, 40, 1, 4, 6, 13, 3, 10, 6, 12, 5, 1, 5, 6, 11, 3, 11,
|
||||||
|
6, 14, 3, 1, 5, 6, 11, 3, 11, 6, 15, 2, 1, 6, 6, 9, 3, 12, 6, 16, 1, 1, 6, 6,
|
||||||
|
9, 3, 12, 6, 7, 1, 10, 7, 6, 7, 3, 13, 6, 6, 2, 10, 7, 6, 7, 3, 13, 14, 10, 8,
|
||||||
|
6, 5, 3, 14, 6, 6, 2, 10, 8, 6, 5, 3, 14, 6, 7, 1, 10, 9, 6, 3, 3, 15, 6, 16, 1,
|
||||||
|
1, 9, 6, 3, 3, 15, 6, 15, 2, 1, 10, 6, 1, 3, 16, 6, 14, 3, 1, 10, 10, 16, 6, 12,
|
||||||
|
5, 1, 11, 8, 13, 27, 1, 11, 8, 13, 27, 1, 60]
|
||||||
|
|
||||||
|
puts 'LOVE'.center(60)
|
||||||
|
puts 'stephan.com'.center(60)
|
||||||
|
puts "\n\n"
|
||||||
|
|
||||||
|
puts <<~EOLOVE
|
||||||
|
A TRIBUTE TO THE GREAT AMERICAN ARTIST, ROBERT INDIANA.
|
||||||
|
HIS GREATEST WORK WILL BE REPRODUCED WITH A MESSAGE OF
|
||||||
|
YOUR CHOICE UP TO 60 CHARACTERS. IF YOU CAN'T THINK OF
|
||||||
|
A MESSAGE, SIMPLY TYPE THE WORD 'LOVE'\n
|
||||||
|
EOLOVE
|
||||||
|
|
||||||
|
message = gets.strip
|
||||||
|
message = 'love' if message.empty?
|
||||||
|
l = message.length
|
||||||
|
|
||||||
|
until data.empty?
|
||||||
|
puts
|
||||||
|
col = 0
|
||||||
|
p = true
|
||||||
|
while col < 60
|
||||||
|
run = data.shift
|
||||||
|
|
||||||
|
if p
|
||||||
|
run.times { |i| print message[(col + i) % l] }
|
||||||
|
else
|
||||||
|
print ' ' * run
|
||||||
|
end
|
||||||
|
p = !p
|
||||||
|
col += run
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||||
|
|
||||||
Conversion to [Perl](https://www.perl.org/)
|
Conversion to [Perl](https://www.perl.org/)
|
||||||
|
|
||||||
|
This is pretty much a re-implementation of the BASIC, taking advantage
|
||||||
|
of Perl's array functionality and working directly with the alphabetic
|
||||||
|
color codes.
|
||||||
|
|||||||
419
60_Mastermind/perl/mastermind.pl
Executable file
419
60_Mastermind/perl/mastermind.pl
Executable file
@@ -0,0 +1,419 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
|
||||||
|
use 5.010; # To get 'state' and 'say'
|
||||||
|
|
||||||
|
use strict; # Require explicit declaration of variables
|
||||||
|
use warnings; # Enable optional compiler warnings
|
||||||
|
|
||||||
|
use English; # Use more friendly names for Perl's magic variables
|
||||||
|
use List::Util qw{ min sum }; # Convenient list utilities
|
||||||
|
use Term::ReadLine; # Prompt and return user input
|
||||||
|
|
||||||
|
our $VERSION = '0.000_01';
|
||||||
|
|
||||||
|
use constant MAX_GUESSES => 10;
|
||||||
|
|
||||||
|
print <<'EOD';
|
||||||
|
MASTERMIND
|
||||||
|
Creative Computing Morristown, New Jersey
|
||||||
|
|
||||||
|
|
||||||
|
EOD
|
||||||
|
|
||||||
|
=begin comment
|
||||||
|
|
||||||
|
MASTERMIND II
|
||||||
|
STEVE NORTH
|
||||||
|
CREATIVE COMPUTING
|
||||||
|
PO BOX 789-M MORRISTOWN NEW JERSEY 07960
|
||||||
|
|
||||||
|
=end comment
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
# NOTE that mixed-case 'my' variables are 'global' in the sense that
|
||||||
|
# they are used in subroutines, but not passed to them.
|
||||||
|
|
||||||
|
say '';
|
||||||
|
|
||||||
|
my $number_of_colors = get_input(
|
||||||
|
'Number of colors [1-8]: ',
|
||||||
|
sub { m/ \A [1-8] \z /smx },
|
||||||
|
"No more than 8, please!\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
say '';
|
||||||
|
|
||||||
|
my $Number_of_Positions = get_input(
|
||||||
|
'Number of positions: ',
|
||||||
|
sub { m/ \A [0-9]+ \z /smx && $ARG },
|
||||||
|
"A positive number, please\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
say '';
|
||||||
|
|
||||||
|
my $number_of_rounds = get_input(
|
||||||
|
'Number of rounds: ',
|
||||||
|
sub { m/ \A [0-9]+ \z /smx && $ARG },
|
||||||
|
"A positive number, please\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
my $P = $number_of_colors ** $Number_of_Positions;
|
||||||
|
say 'Total possibilities = ', $P;
|
||||||
|
|
||||||
|
my @colors = ( qw{
|
||||||
|
Black White Red Green Orange Yellow Purple Tan
|
||||||
|
})[ 0 .. $number_of_colors - 1 ];
|
||||||
|
my @Color_Codes = map { uc substr $ARG, 0, 1 } @colors;
|
||||||
|
|
||||||
|
print <<'EOD';
|
||||||
|
|
||||||
|
|
||||||
|
Color Letter
|
||||||
|
===== ======
|
||||||
|
EOD
|
||||||
|
|
||||||
|
foreach my $inx ( 0 .. $#colors ) {
|
||||||
|
printf "%-13s%s\n", $colors[$inx], $Color_Codes[$inx];
|
||||||
|
}
|
||||||
|
|
||||||
|
say '';
|
||||||
|
|
||||||
|
my $computer_score = 0; # Computer score
|
||||||
|
my $human_score = 0; # Human score
|
||||||
|
|
||||||
|
foreach my $round_number ( 1 .. $number_of_rounds ) {
|
||||||
|
|
||||||
|
print <<"EOD";
|
||||||
|
|
||||||
|
Round number $round_number ----
|
||||||
|
|
||||||
|
Guess my combination.
|
||||||
|
|
||||||
|
EOD
|
||||||
|
|
||||||
|
$human_score += human_guesses( $Number_of_Positions );
|
||||||
|
|
||||||
|
print_score( $computer_score, $human_score );
|
||||||
|
|
||||||
|
$computer_score += computer_guesses();
|
||||||
|
|
||||||
|
print_score( $computer_score, $human_score );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make a $pattern into a hash with one key for each possible color. The
|
||||||
|
# value for each color is the number of times it appears in the pattern.
|
||||||
|
sub hashify_pattern {
|
||||||
|
my $pattern = uc $ARG[0];
|
||||||
|
my %p = map { $ARG => 0 } @Color_Codes;
|
||||||
|
$p{$ARG}++ for split qr//, $pattern;
|
||||||
|
return \%p;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Given a $pattern, a $guess at that pattern, and $black and $white
|
||||||
|
# scores, return a true value if the $black and $white scores of the
|
||||||
|
# $guess are those supplied as arguments; otherwise return a false
|
||||||
|
# value. This is used by computer_guesses() to eliminate possibilities.
|
||||||
|
sub analyze_black_white {
|
||||||
|
my ( $pattern, $guess, $black, $white ) = @ARG;
|
||||||
|
my $info = analyze_guess( $pattern, $guess );
|
||||||
|
return $info->{black} == $black && $info->{white} == $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Given a $pattern and a $guess at that pattern, return a reference to a
|
||||||
|
# hash with the following keys:
|
||||||
|
# {guess} is the guess;
|
||||||
|
# {black} is the black score of the guess
|
||||||
|
# {white} is the white score of the guess
|
||||||
|
sub analyze_guess {
|
||||||
|
my ( $pattern, $guess ) = @ARG;
|
||||||
|
my $pattern_hash = hashify_pattern( $pattern );
|
||||||
|
my $guess_hash = hashify_pattern( $guess );
|
||||||
|
my $white = sum(
|
||||||
|
map { min( $pattern_hash->{$ARG}, $guess_hash->{$ARG} ) } @Color_Codes,
|
||||||
|
);
|
||||||
|
my $black = 0;
|
||||||
|
foreach my $inx ( 0 .. length( $pattern ) - 1 ) {
|
||||||
|
if ( substr( $pattern, $inx, 1 ) eq substr( $guess, $inx, 1 ) )
|
||||||
|
{
|
||||||
|
$black++;
|
||||||
|
--$white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return +{
|
||||||
|
guess => $guess,
|
||||||
|
black => $black,
|
||||||
|
white => $white,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Used by the computer to guess the human's choice. The return is the
|
||||||
|
# number of guesses the computer took. The return is the maximum plus
|
||||||
|
# one if the computer failed to guess.
|
||||||
|
sub computer_guesses {
|
||||||
|
|
||||||
|
print <<'EOD';
|
||||||
|
|
||||||
|
Now I guess. Think of a combination.
|
||||||
|
EOD
|
||||||
|
get_input(
|
||||||
|
'Hit <return> when ready:',
|
||||||
|
);
|
||||||
|
|
||||||
|
# Generate all possible permutations.
|
||||||
|
my @possible;
|
||||||
|
foreach my $permutation ( 0 .. @Color_Codes ** $Number_of_Positions - 1 ) {
|
||||||
|
my $guess;
|
||||||
|
for ( 1 .. $Number_of_Positions ) {
|
||||||
|
my $inx = $permutation % @Color_Codes;
|
||||||
|
$guess .= $Color_Codes[ $inx ];
|
||||||
|
$permutation = int( $permutation / @Color_Codes );
|
||||||
|
}
|
||||||
|
push @possible, $guess;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Guess ...
|
||||||
|
foreach my $guess_num ( 1 .. MAX_GUESSES ) {
|
||||||
|
|
||||||
|
# Guess a possible permutation at random, removing it from the
|
||||||
|
# list.
|
||||||
|
my $guess = splice @possible, int rand @possible, 1;
|
||||||
|
say 'My guess is: ', $guess;
|
||||||
|
|
||||||
|
# Find out its black/white score.
|
||||||
|
my ( $black, $white ) = split qr< , >smx, get_input(
|
||||||
|
'Blacks, Whites: ',
|
||||||
|
sub { m/ \A [0-9]+ , [0-9]+ \z /smx },
|
||||||
|
"Please enter two unsigned integers\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
# If it's all black, the computer wins.
|
||||||
|
if ( $black == $Number_of_Positions ) {
|
||||||
|
say "I got it in $guess_num moves!";
|
||||||
|
return $guess_num;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Eliminate all possible permutations that give the black/white
|
||||||
|
# score that our guess got. If there are any left, take another
|
||||||
|
# guess.
|
||||||
|
next if @possible = grep { analyze_black_white( $ARG, $guess, $black,
|
||||||
|
$white ) } @possible;
|
||||||
|
|
||||||
|
# There were no permutations left. Complain.
|
||||||
|
print <<'EOD';
|
||||||
|
You have given me inconsistent information.
|
||||||
|
Try again, and this time please be more careful.
|
||||||
|
EOD
|
||||||
|
|
||||||
|
goto &computer_guesses; # Tail-call ourselves to try again.
|
||||||
|
}
|
||||||
|
|
||||||
|
print <<'EOD';
|
||||||
|
I used up all my moves!
|
||||||
|
I guess my CPU is just having an off day.
|
||||||
|
EOD
|
||||||
|
|
||||||
|
return MAX_GUESSES + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Used to generate a pattern and process the human's guesses. The return
|
||||||
|
# is the number of guesses the human took. The return is the maximum
|
||||||
|
# plus one if the human failed to guess.
|
||||||
|
sub human_guesses {
|
||||||
|
|
||||||
|
my @saved_moves; # Saved moves
|
||||||
|
my $pattern = uc join '',
|
||||||
|
map { $Color_Codes[ rand @Color_Codes ] } 1 .. $Number_of_Positions;
|
||||||
|
|
||||||
|
foreach my $guess_num ( 1 .. MAX_GUESSES ) {
|
||||||
|
|
||||||
|
my $guess = uc get_input(
|
||||||
|
"Move # $guess_num guess: ",
|
||||||
|
sub {
|
||||||
|
|
||||||
|
# If the user entered 'quit', bail out.
|
||||||
|
if ( m/ \A quit \z /smxi ) {
|
||||||
|
die "Quitter! My combination was $pattern\n\nGood bye\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
# If the user entered 'board', display the board so far.
|
||||||
|
# We return success to prevent the warning message, but
|
||||||
|
# we also clear $ARG. The caller's caller sees this and
|
||||||
|
# re-queries.
|
||||||
|
if ( m/ \A board \z /smxi ) {
|
||||||
|
print <<'EOD';
|
||||||
|
|
||||||
|
Board
|
||||||
|
Move Guess Black White
|
||||||
|
EOD
|
||||||
|
my $number = 1;
|
||||||
|
foreach my $item ( @saved_moves ) {
|
||||||
|
printf "%4d %-13s %3d %3d\n", $number++,
|
||||||
|
@{ $item }{ qw{ guess black white } };
|
||||||
|
}
|
||||||
|
return undef; # Validation failure, but suppress warning.
|
||||||
|
}
|
||||||
|
|
||||||
|
# End of special-case code. Below here we are dealing
|
||||||
|
# with guess input.
|
||||||
|
|
||||||
|
# The length of the input must equal the number of
|
||||||
|
# positions.
|
||||||
|
if ( $Number_of_Positions != length ) {
|
||||||
|
warn "Bad number of positions\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
# The input may contain only valid color codes.
|
||||||
|
state $invalid_color = do { # Evaluated only once
|
||||||
|
local $LIST_SEPARATOR = '';
|
||||||
|
qr< [^@Color_Codes] >smxi;
|
||||||
|
};
|
||||||
|
if ( m/ ( $invalid_color ) /smxi ) {
|
||||||
|
warn "'$1' is unrecognized.\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
# We're good.
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
"Please enter 'board', 'quit', or any $Number_of_Positions of @{[
|
||||||
|
join ', ', map { qq<'$ARG'> } @Color_Codes ]}.\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
my $rslt = analyze_guess( $pattern, $guess );
|
||||||
|
|
||||||
|
push @saved_moves, $rslt;
|
||||||
|
|
||||||
|
if ( $rslt->{black} == $Number_of_Positions ) {
|
||||||
|
say "You guessed it in $guess_num moves.";
|
||||||
|
return $guess_num;
|
||||||
|
}
|
||||||
|
|
||||||
|
say "You have $rslt->{black} blacks and $rslt->{white} whites.";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
print <<"EOD";
|
||||||
|
You ran out of moves. That's all you get.
|
||||||
|
|
||||||
|
The actual combination was: $pattern
|
||||||
|
EOD
|
||||||
|
|
||||||
|
return MAX_GUESSES + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print the $computer and $human score
|
||||||
|
sub print_score {
|
||||||
|
my ( $computer, $human ) = @ARG;
|
||||||
|
print <<"EOD";
|
||||||
|
Score:
|
||||||
|
Computer: $computer
|
||||||
|
Human: $human
|
||||||
|
EOD
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get input from the user. The arguments are:
|
||||||
|
# * The prompt
|
||||||
|
# * A reference to validation code. This code receives the response in
|
||||||
|
# $ARG and returns true for a valid response.
|
||||||
|
# * A warning to print if the response is not valid. This must end in a
|
||||||
|
# return. It is suppressed if the validation code returned undef.
|
||||||
|
# The first valid response is returned. An end-of-file terminates the
|
||||||
|
# script.
|
||||||
|
sub get_input {
|
||||||
|
my ( $prompt, $validate, $warning ) = @ARG;
|
||||||
|
|
||||||
|
# If no validator is passed, default to one that always returns
|
||||||
|
# true.
|
||||||
|
$validate ||= sub { 1 };
|
||||||
|
|
||||||
|
# Create the readline object. The 'state' causes the variable to be
|
||||||
|
# initialized only once, no matter how many times this subroutine is
|
||||||
|
# called. The do { ... } is a compound statement used because we
|
||||||
|
# need to tweak the created object before we store it.
|
||||||
|
state $term = do {
|
||||||
|
my $obj = Term::ReadLine->new( 'reverse' );
|
||||||
|
$obj->ornaments( 0 );
|
||||||
|
$obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
while ( 1 ) { # Iterate indefinitely
|
||||||
|
|
||||||
|
# Read the input into the topic variable, localized to prevent
|
||||||
|
# Spooky Action at a Distance. We exit on undef, which signals
|
||||||
|
# end-of-file.
|
||||||
|
exit unless defined( local $ARG = $term->readline( $prompt ) );
|
||||||
|
|
||||||
|
# Return the input if it is valid.
|
||||||
|
return $ARG if my $rslt = $validate->();
|
||||||
|
|
||||||
|
# Issue the warning, and go around the merry-go-round again.
|
||||||
|
warn $warning if defined $rslt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# NOTE the following is unused, but left in place in case someone wants
|
||||||
|
# to add a 'Do you want instructions?'
|
||||||
|
#
|
||||||
|
# Get a yes-or-no answer. The argument is the prompt, which will have
|
||||||
|
# '? [y/n]: ' appended. The donkey work is done by get_input(), which is
|
||||||
|
# requested to validate the response as beginning with 'y' or 'n',
|
||||||
|
# case-insensitive. The return is a true value for 'y' and a false value
|
||||||
|
# for 'n'.
|
||||||
|
sub get_yes_no {
|
||||||
|
my ( $prompt ) = @ARG;
|
||||||
|
state $map_answer = {
|
||||||
|
n => 0,
|
||||||
|
y => 1,
|
||||||
|
};
|
||||||
|
my $resp = lc get_input(
|
||||||
|
"$prompt? [y/n]: ",
|
||||||
|
sub { m/ \A [yn] /smxi },
|
||||||
|
"Please respond 'y' or 'n'\n",
|
||||||
|
);
|
||||||
|
return $map_answer->{ substr $resp, 0, 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
__END__
|
||||||
|
|
||||||
|
=head1 TITLE
|
||||||
|
|
||||||
|
mastermind - Play the game 'Mastermind' from Basic Computer Games
|
||||||
|
|
||||||
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
mastermind.pl
|
||||||
|
|
||||||
|
=head1 DETAILS
|
||||||
|
|
||||||
|
This Perl script is a port of mastermind, which is the 60th
|
||||||
|
entry in Basic Computer Games.
|
||||||
|
|
||||||
|
This is pretty much a re-implementation of the BASIC, taking advantage
|
||||||
|
of Perl's array functionality and working directly with the alphabetic
|
||||||
|
color codes.
|
||||||
|
|
||||||
|
=head1 PORTED BY
|
||||||
|
|
||||||
|
Thomas R. Wyant, III F<wyant at cpan dot org>
|
||||||
|
|
||||||
|
=head1 COPYRIGHT AND LICENSE
|
||||||
|
|
||||||
|
Copyright (C) 2022 by Thomas R. Wyant, III
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify it
|
||||||
|
under the same terms as Perl 5.10.0. For more details, see the Artistic
|
||||||
|
License 1.0 at
|
||||||
|
L<https://www.perlfoundation.org/artistic-license-10.html>, and/or the
|
||||||
|
Gnu GPL at L<http://www.gnu.org/licenses/old-licenses/gpl-1.0.txt>.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but
|
||||||
|
without any warranty; without even the implied warranty of
|
||||||
|
merchantability or fitness for a particular purpose.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
# ex: set expandtab tabstop=4 textwidth=72 :
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#!/usr/bin/perl
|
|
||||||
#use strict;
|
|
||||||
# Automatic converted by bas2perl.pl
|
|
||||||
|
|
||||||
print ' 'x28 . "RUSSIAN ROULETTE\n";
|
|
||||||
print ' 'x15 . "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n";
|
|
||||||
print "\n"; print "\n"; print "\n";
|
|
||||||
print "THIS IS A GAME OF >>>>>>>>>>RUSSIAN ROULETTE.\n";
|
|
||||||
Line10:
|
|
||||||
print "\n"; print "HERE IS A REVOLVER.\n";
|
|
||||||
Line20:
|
|
||||||
print "TYPE '1' TO SPIN CHAMBER AND PULL TRIGGER.\n";
|
|
||||||
print "TYPE '2' TO GIVE UP.\n";
|
|
||||||
print "GO";
|
|
||||||
$N=0;
|
|
||||||
Line30:
|
|
||||||
print "? "; chomp($I = <STDIN>);
|
|
||||||
if ($I ne 2) { goto Line35; }
|
|
||||||
print " CHICKEN!!!!!\n";
|
|
||||||
goto Line72;
|
|
||||||
Line35:
|
|
||||||
$N=$N+1;
|
|
||||||
if (rand(1)>.833333) { goto Line70; }
|
|
||||||
if ($N>10) { goto Line80; }
|
|
||||||
print "- CLICK -\n";
|
|
||||||
print "\n"; goto Line30;
|
|
||||||
Line70:
|
|
||||||
print " BANG!!!!! YOU'RE DEAD!\n";
|
|
||||||
print "CONDOLENCES WILL BE SENT TO YOUR RELATIVES.\n";
|
|
||||||
Line72:
|
|
||||||
print "\n"; print "\n"; print "\n";
|
|
||||||
print "...NEXT VICTIM...\n"; goto Line20;
|
|
||||||
Line80:
|
|
||||||
print "YOU WIN!!!!!\n";
|
|
||||||
print "LET SOMEONE ELSE BLOW HIS BRAINS OUT.\n";
|
|
||||||
goto Line10;
|
|
||||||
exit;
|
|
||||||
|
|
||||||
|
|
||||||
426
83_Stock_Market/java/StockMarket.java
Normal file
426
83_Stock_Market/java/StockMarket.java
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.InputMismatchException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stock Market Simulation
|
||||||
|
*
|
||||||
|
* Some of the original program's variables' documentation and their equivalent in this program:
|
||||||
|
* A-MRKT TRND SLP; marketTrendSlope
|
||||||
|
* B5-BRKRGE FEE; brokerageFee
|
||||||
|
* C-TTL CSH ASSTS; cashAssets
|
||||||
|
* C5-TTL CSH ASSTS (TEMP); tmpCashAssets
|
||||||
|
* C(I)-CHNG IN STK VAL; changeStockValue
|
||||||
|
* D-TTL ASSTS; assets
|
||||||
|
* E1,E2-LRG CHNG MISC; largeChange1, largeChange2
|
||||||
|
* I1,I2-STCKS W LRG CHNG; randomStockIndex1, randomStockIndex2
|
||||||
|
* N1,N2-LRG CHNG DAY CNTS; largeChangeNumberDays1, largeChangeNumberDays2
|
||||||
|
* P5-TTL DAYS PRCHSS; totalDaysPurchases
|
||||||
|
* P(I)-PRTFL CNTNTS; portfolioContents
|
||||||
|
* Q9-NEW CYCL?; newCycle
|
||||||
|
* S4-SGN OF A; slopeSign
|
||||||
|
* S5-TTL DYS SLS; totalDaysSales
|
||||||
|
* S(I)-VALUE/SHR; stockValue
|
||||||
|
* T-TTL STCK ASSTS; totalStockAssets
|
||||||
|
* T5-TTL VAL OF TRNSCTNS; totalValueOfTransactions
|
||||||
|
* W3-LRG CHNG; bigChange
|
||||||
|
* X1-SMLL CHNG(<$1); smallChange
|
||||||
|
* Z4,Z5,Z6-NYSE AVE.; tmpNyseAverage, nyseAverage, nyseAverageChange
|
||||||
|
* Z(I)-TRNSCT transactionQuantity
|
||||||
|
*
|
||||||
|
* new price = old price + (trend x old price) + (small random price
|
||||||
|
* change) + (possible large price change)
|
||||||
|
*
|
||||||
|
* Converted from BASIC to Java by Aldrin Misquitta (@aldrinm)
|
||||||
|
*/
|
||||||
|
public class StockMarket {
|
||||||
|
|
||||||
|
private static final Random random = new Random();
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
Scanner scan = new Scanner(System.in);
|
||||||
|
|
||||||
|
printIntro();
|
||||||
|
printGameHelp(scan);
|
||||||
|
|
||||||
|
final List<Stock> stocks = initStocks();
|
||||||
|
|
||||||
|
double marketTrendSlope = Math.floor((random.nextFloat() / 10) * 100 + 0.5)/100f;
|
||||||
|
double totalValueOfTransactions;
|
||||||
|
int largeChangeNumberDays1 = 0;
|
||||||
|
int largeChangeNumberDays2 = 0;
|
||||||
|
|
||||||
|
//DAYS FOR FIRST TREND SLOPE (A)
|
||||||
|
var t8 = randomNumber(1, 6);
|
||||||
|
|
||||||
|
//RANDOMIZE SIGN OF FIRST TREND SLOPE (A)
|
||||||
|
if (random.nextFloat() <= 0.5) {
|
||||||
|
marketTrendSlope = -marketTrendSlope;
|
||||||
|
}
|
||||||
|
|
||||||
|
// INITIALIZE CASH ASSETS:C
|
||||||
|
double cashAssets = 10000;
|
||||||
|
boolean largeChange1 = false;
|
||||||
|
boolean largeChange2 = false;
|
||||||
|
double tmpNyseAverage;
|
||||||
|
double nyseAverage = 0;
|
||||||
|
boolean inProgress = true;
|
||||||
|
var firstRound = true;
|
||||||
|
|
||||||
|
while (inProgress) {
|
||||||
|
|
||||||
|
/* Original documentation:
|
||||||
|
RANDOMLY PRODUCE NEW STOCK VALUES BASED ON PREVIOUS DAY'S VALUES
|
||||||
|
N1,N2 ARE RANDOM NUMBERS OF DAYS WHICH RESPECTIVELY
|
||||||
|
DETERMINE WHEN STOCK I1 WILL INCREASE 10 PTS. AND STOCK
|
||||||
|
I2 WILL DECREASE 10 PTS.
|
||||||
|
IF N1 DAYS HAVE PASSED, PICK AN I1, SET E1, DETERMINE NEW N1
|
||||||
|
*/
|
||||||
|
int randomStockIndex1 = 0;
|
||||||
|
int randomStockIndex2 = 0;
|
||||||
|
|
||||||
|
if (largeChangeNumberDays1 <= 0) {
|
||||||
|
randomStockIndex1 = randomNumber(0, stocks.size());
|
||||||
|
largeChangeNumberDays1 = randomNumber(1, 6);
|
||||||
|
largeChange1 = true;
|
||||||
|
}
|
||||||
|
if (largeChangeNumberDays2 <= 0) {
|
||||||
|
randomStockIndex2 = randomNumber(0, stocks.size());
|
||||||
|
largeChangeNumberDays2 = randomNumber(1, 6);
|
||||||
|
largeChange2 = true;
|
||||||
|
}
|
||||||
|
adjustAllStockValues(stocks, largeChange1, largeChange2, marketTrendSlope, stocks.get(randomStockIndex1), stocks.get(randomStockIndex2));
|
||||||
|
|
||||||
|
//reset largeChange flags
|
||||||
|
largeChange1 = false;
|
||||||
|
largeChange2 = false;
|
||||||
|
largeChangeNumberDays1--;
|
||||||
|
largeChangeNumberDays2--;
|
||||||
|
|
||||||
|
//AFTER T8 DAYS RANDOMLY CHANGE TREND SIGN AND SLOPE
|
||||||
|
t8 = t8 - 1;
|
||||||
|
if (t8 < 1) {
|
||||||
|
marketTrendSlope = newMarketTrendSlope();
|
||||||
|
t8 = randomNumber(1, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
//PRINT PORTFOLIO
|
||||||
|
printPortfolio(firstRound, stocks);
|
||||||
|
|
||||||
|
tmpNyseAverage = nyseAverage;
|
||||||
|
nyseAverage = 0;
|
||||||
|
double totalStockAssets = 0;
|
||||||
|
for (Stock stock : stocks) {
|
||||||
|
nyseAverage = nyseAverage + stock.getStockValue();
|
||||||
|
totalStockAssets = totalStockAssets + stock.getStockValue() * stock.getPortfolioContents();
|
||||||
|
}
|
||||||
|
nyseAverage = Math.floor(100 * (nyseAverage / 5) + .5) / 100f;
|
||||||
|
double nyseAverageChange = Math.floor((nyseAverage - tmpNyseAverage) * 100 + .5) / 100f;
|
||||||
|
|
||||||
|
// TOTAL ASSETS:D
|
||||||
|
double assets = totalStockAssets + cashAssets;
|
||||||
|
if (firstRound) {
|
||||||
|
System.out.printf("\n\nNEW YORK STOCK EXCHANGE AVERAGE: %.2f", nyseAverage);
|
||||||
|
} else {
|
||||||
|
System.out.printf("\n\nNEW YORK STOCK EXCHANGE AVERAGE: %.2f NET CHANGE %.2f", nyseAverage, nyseAverageChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
totalStockAssets = Math.floor(100 * totalStockAssets + 0.5) / 100d;
|
||||||
|
System.out.printf("\n\nTOTAL STOCK ASSETS ARE $ %.2f", totalStockAssets);
|
||||||
|
cashAssets = Math.floor(100 * cashAssets + 0.5) / 100d;
|
||||||
|
System.out.printf("\nTOTAL CASH ASSETS ARE $ %.2f", cashAssets);
|
||||||
|
assets = Math.floor(100 * assets + .5) / 100d;
|
||||||
|
System.out.printf("\nTOTAL ASSETS ARE $ %.2f\n", assets);
|
||||||
|
|
||||||
|
if (!firstRound) {
|
||||||
|
System.out.print("\nDO YOU WISH TO CONTINUE (YES-TYPE 1, NO-TYPE 0)? ");
|
||||||
|
var newCycle = readANumber(scan);
|
||||||
|
if (newCycle < 1) {
|
||||||
|
System.out.println("HOPE YOU HAD FUN!!");
|
||||||
|
inProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inProgress) {
|
||||||
|
boolean validTransaction = false;
|
||||||
|
// TOTAL DAY'S PURCHASES IN $:P5
|
||||||
|
double totalDaysPurchases = 0;
|
||||||
|
// TOTAL DAY'S SALES IN $:S5
|
||||||
|
double totalDaysSales = 0;
|
||||||
|
double tmpCashAssets;
|
||||||
|
while (!validTransaction) {
|
||||||
|
//INPUT TRANSACTIONS
|
||||||
|
readStockTransactions(stocks, scan);
|
||||||
|
totalDaysPurchases = 0;
|
||||||
|
totalDaysSales = 0;
|
||||||
|
|
||||||
|
validTransaction = true;
|
||||||
|
for (Stock stock : stocks) {
|
||||||
|
stock.setTransactionQuantity(Math.floor(stock.getTransactionQuantity() + 0.5));
|
||||||
|
if (stock.getTransactionQuantity() > 0) {
|
||||||
|
totalDaysPurchases = totalDaysPurchases + stock.getTransactionQuantity() * stock.getStockValue();
|
||||||
|
} else {
|
||||||
|
totalDaysSales = totalDaysSales - stock.getTransactionQuantity() * stock.getStockValue();
|
||||||
|
if (-stock.getTransactionQuantity() > stock.getPortfolioContents()) {
|
||||||
|
System.out.println("YOU HAVE OVERSOLD A STOCK; TRY AGAIN.");
|
||||||
|
validTransaction = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TOTAL VALUE OF TRANSACTIONS:T5
|
||||||
|
totalValueOfTransactions = totalDaysPurchases + totalDaysSales;
|
||||||
|
// BROKERAGE FEE:B5
|
||||||
|
var brokerageFee = Math.floor(0.01 * totalValueOfTransactions * 100 + .5) / 100d;
|
||||||
|
// CASH ASSETS=OLD CASH ASSETS-TOTAL PURCHASES
|
||||||
|
//-BROKERAGE FEES+TOTAL SALES:C5
|
||||||
|
tmpCashAssets = cashAssets - totalDaysPurchases - brokerageFee + totalDaysSales;
|
||||||
|
if (tmpCashAssets < 0) {
|
||||||
|
System.out.printf("\nYOU HAVE USED $%.2f MORE THAN YOU HAVE.", -tmpCashAssets);
|
||||||
|
validTransaction = false;
|
||||||
|
} else {
|
||||||
|
cashAssets = tmpCashAssets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CALCULATE NEW PORTFOLIO
|
||||||
|
for (Stock stock : stocks) {
|
||||||
|
stock.setPortfolioContents(stock.getPortfolioContents() + stock.getTransactionQuantity());
|
||||||
|
}
|
||||||
|
|
||||||
|
firstRound = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Random int between lowerBound(inclusive) and upperBound(exclusive)
|
||||||
|
*/
|
||||||
|
private static int randomNumber(int lowerBound, int upperBound) {
|
||||||
|
return random.nextInt((upperBound - lowerBound)) + lowerBound;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double newMarketTrendSlope() {
|
||||||
|
return randomlyChangeTrendSignAndSlopeAndDuration();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void printPortfolio(boolean firstRound, List<Stock> stocks) {
|
||||||
|
//BELL RINGING-DIFFERENT ON MANY COMPUTERS
|
||||||
|
if (firstRound) {
|
||||||
|
System.out.printf("%n%-30s\t%12s\t%12s", "STOCK", "INITIALS", "PRICE/SHARE");
|
||||||
|
for (Stock stock : stocks) {
|
||||||
|
System.out.printf("%n%-30s\t%12s\t%12.2f ------ %12.2f", stock.getStockName(), stock.getStockCode(),
|
||||||
|
stock.getStockValue(), stock.getChangeStockValue());
|
||||||
|
}
|
||||||
|
System.out.println("");
|
||||||
|
} else {
|
||||||
|
System.out.println("\n********** END OF DAY'S TRADING **********\n\n");
|
||||||
|
System.out.printf("%n%-12s\t%-12s\t%-12s\t%-12s\t%-20s", "STOCK", "PRICE/SHARE",
|
||||||
|
"HOLDINGS", "VALUE", "NET PRICE CHANGE");
|
||||||
|
for (Stock stock : stocks) {
|
||||||
|
System.out.printf("%n%-12s\t%-12.2f\t%-12.0f\t%-12.2f\t%-20.2f",
|
||||||
|
stock.getStockCode(), stock.getStockValue(), stock.getPortfolioContents(),
|
||||||
|
stock.getStockValue() * stock.getPortfolioContents(), stock.getChangeStockValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void readStockTransactions(List<Stock> stocks, Scanner scan) {
|
||||||
|
System.out.println("\n\nWHAT IS YOUR TRANSACTION IN");
|
||||||
|
for (Stock stock : stocks) {
|
||||||
|
System.out.printf("%s? ", stock.getStockCode());
|
||||||
|
|
||||||
|
stock.setTransactionQuantity(readANumber(scan));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int readANumber(Scanner scan) {
|
||||||
|
int choice = 0;
|
||||||
|
|
||||||
|
boolean validInput = false;
|
||||||
|
while (!validInput) {
|
||||||
|
try {
|
||||||
|
choice = scan.nextInt();
|
||||||
|
validInput = true;
|
||||||
|
} catch (InputMismatchException ex) {
|
||||||
|
System.out.println("!NUMBER EXPECTED - RETRY INPUT LINE");
|
||||||
|
} finally {
|
||||||
|
scan.nextLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return choice;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void adjustAllStockValues(List<Stock> stocks, boolean largeChange1,
|
||||||
|
boolean largeChange2,
|
||||||
|
double marketTrendSlope,
|
||||||
|
Stock stockForLargeChange1, Stock stockForLargeChange2
|
||||||
|
) {
|
||||||
|
//LOOP THROUGH ALL STOCKS
|
||||||
|
for (Stock stock : stocks) {
|
||||||
|
double smallChange = random.nextFloat();
|
||||||
|
|
||||||
|
if (smallChange <= 0.25) {
|
||||||
|
smallChange = 0.25;
|
||||||
|
} else if (smallChange <= 0.5) {
|
||||||
|
smallChange = 0.5;
|
||||||
|
} else if (smallChange <= 0.75) {
|
||||||
|
smallChange = 0.75;
|
||||||
|
} else {
|
||||||
|
smallChange = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//BIG CHANGE CONSTANT:W3 (SET TO ZERO INITIALLY)
|
||||||
|
var bigChange = 0;
|
||||||
|
if (largeChange1) {
|
||||||
|
if (stock.getStockCode().equals(stockForLargeChange1.getStockCode())) {
|
||||||
|
//ADD 10 PTS. TO THIS STOCK; RESET E1
|
||||||
|
bigChange = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (largeChange2) {
|
||||||
|
if (stock.getStockCode().equals(stockForLargeChange2.getStockCode())) {
|
||||||
|
//SUBTRACT 10 PTS. FROM THIS STOCK; RESET E2
|
||||||
|
bigChange = bigChange - 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stock.setChangeStockValue(Math.floor(marketTrendSlope * stock.stockValue) + smallChange +
|
||||||
|
Math.floor(3 - 6 * random.nextFloat() + .5) + bigChange);
|
||||||
|
stock.setChangeStockValue(Math.floor(100 * stock.getChangeStockValue() + .5) / 100d);
|
||||||
|
stock.stockValue += stock.getChangeStockValue();
|
||||||
|
|
||||||
|
if (stock.stockValue > 0) {
|
||||||
|
stock.stockValue = Math.floor(100 * stock.stockValue + 0.5) / 100d;
|
||||||
|
} else {
|
||||||
|
stock.setChangeStockValue(0);
|
||||||
|
stock.stockValue = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double randomlyChangeTrendSignAndSlopeAndDuration() {
|
||||||
|
// RANDOMLY CHANGE TREND SIGN AND SLOPE (A), AND DURATION
|
||||||
|
var newTrend = Math.floor((random.nextFloat() / 10) * 100 + .5) / 100d;
|
||||||
|
var slopeSign = random.nextFloat();
|
||||||
|
if (slopeSign > 0.5) {
|
||||||
|
newTrend = -newTrend;
|
||||||
|
}
|
||||||
|
return newTrend;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Stock> initStocks() {
|
||||||
|
List<Stock> stocks = new ArrayList<>();
|
||||||
|
stocks.add(new Stock(100, "INT. BALLISTIC MISSILES", "IBM"));
|
||||||
|
stocks.add(new Stock(85, "RED CROSS OF AMERICA", "RCA"));
|
||||||
|
stocks.add(new Stock(150, "LICHTENSTEIN, BUMRAP & JOKE", "LBJ"));
|
||||||
|
stocks.add(new Stock(140, "AMERICAN BANKRUPT CO.", "ABC"));
|
||||||
|
stocks.add(new Stock(110, "CENSURED BOOKS STORE", "CBS"));
|
||||||
|
return stocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void printGameHelp(Scanner scan) {
|
||||||
|
System.out.print("DO YOU WANT THE INSTRUCTIONS (YES-TYPE 1, NO-TYPE 0) ? ");
|
||||||
|
int choice = scan.nextInt();
|
||||||
|
if (choice >= 1) {
|
||||||
|
System.out.println("");
|
||||||
|
System.out.println("THIS PROGRAM PLAYS THE STOCK MARKET. YOU WILL BE GIVEN");
|
||||||
|
System.out.println("$10,000 AND MAY BUY OR SELL STOCKS. THE STOCK PRICES WILL");
|
||||||
|
System.out.println("BE GENERATED RANDOMLY AND THEREFORE THIS MODEL DOES NOT");
|
||||||
|
System.out.println("REPRESENT EXACTLY WHAT HAPPENS ON THE EXCHANGE. A TABLE");
|
||||||
|
System.out.println("OF AVAILABLE STOCKS, THEIR PRICES, AND THE NUMBER OF SHARES");
|
||||||
|
System.out.println("IN YOUR PORTFOLIO WILL BE PRINTED. FOLLOWING THIS, THE");
|
||||||
|
System.out.println("INITIALS OF EACH STOCK WILL BE PRINTED WITH A QUESTION");
|
||||||
|
System.out.println("MARK. HERE YOU INDICATE A TRANSACTION. TO BUY A STOCK");
|
||||||
|
System.out.println("TYPE +NNN, TO SELL A STOCK TYPE -NNN, WHERE NNN IS THE");
|
||||||
|
System.out.println("NUMBER OF SHARES. A BROKERAGE FEE OF 1% WILL BE CHARGED");
|
||||||
|
System.out.println("ON ALL TRANSACTIONS. NOTE THAT IF A STOCK'S VALUE DROPS");
|
||||||
|
System.out.println("TO ZERO IT MAY REBOUND TO A POSITIVE VALUE AGAIN. YOU");
|
||||||
|
System.out.println("HAVE $10,000 TO INVEST. USE INTEGERS FOR ALL YOUR INPUTS.");
|
||||||
|
System.out.println("(NOTE: TO GET A 'FEEL' FOR THE MARKET RUN FOR AT LEAST");
|
||||||
|
System.out.println("10 DAYS)");
|
||||||
|
System.out.println("-----GOOD LUCK!-----");
|
||||||
|
}
|
||||||
|
System.out.println("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void printIntro() {
|
||||||
|
System.out.println(" STOCK MARKET");
|
||||||
|
System.out.println(" CREATIVE COMPUTING MORRISTOWN, NEW JERSEY");
|
||||||
|
System.out.println("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stock class also storing the stock information and other related information for simplicity
|
||||||
|
*/
|
||||||
|
private static class Stock {
|
||||||
|
|
||||||
|
private final String stockName;
|
||||||
|
private final String stockCode;
|
||||||
|
private double stockValue;
|
||||||
|
private double portfolioContents = 0;
|
||||||
|
private double transactionQuantity = 0;
|
||||||
|
private double changeStockValue = 0;
|
||||||
|
|
||||||
|
public Stock(double stockValue, String stockName, String stockCode) {
|
||||||
|
this.stockValue = stockValue;
|
||||||
|
this.stockName = stockName;
|
||||||
|
this.stockCode = stockCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStockName() {
|
||||||
|
return stockName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStockCode() {
|
||||||
|
return stockCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getStockValue() {
|
||||||
|
return stockValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getPortfolioContents() {
|
||||||
|
return portfolioContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPortfolioContents(double portfolioContents) {
|
||||||
|
this.portfolioContents = portfolioContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getTransactionQuantity() {
|
||||||
|
return transactionQuantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTransactionQuantity(double transactionQuantity) {
|
||||||
|
this.transactionQuantity = transactionQuantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getChangeStockValue() {
|
||||||
|
return changeStockValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChangeStockValue(double changeStockValue) {
|
||||||
|
this.changeStockValue = changeStockValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Stock{" +
|
||||||
|
"stockValue=" + stockValue +
|
||||||
|
", stockCode='" + stockCode + '\'' +
|
||||||
|
", portfolioContents=" + portfolioContents +
|
||||||
|
", transactionQuantity=" + transactionQuantity +
|
||||||
|
", changeStockValue=" + changeStockValue +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -110,7 +110,7 @@ sub check_for_corners {
|
|||||||
@precedence=(1,9,7,3,5,2,4,6,8);
|
@precedence=(1,9,7,3,5,2,4,6,8);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@precedence=(5,1,9,7,3,2,4,6,8);
|
@precedence=(5,2,4,6,8,1,9,7,3);
|
||||||
}
|
}
|
||||||
foreach my $move (@precedence) {
|
foreach my $move (@precedence) {
|
||||||
my $validity=&check_occupation($move);
|
my $validity=&check_occupation($move);
|
||||||
@@ -166,7 +166,7 @@ sub check_occupation {
|
|||||||
|
|
||||||
sub print_board {
|
sub print_board {
|
||||||
foreach my $num (1..9) {
|
foreach my $num (1..9) {
|
||||||
my $char = &which_char($board{$num});
|
my $char = &which_char($board{$num});
|
||||||
if ($num == 4 || $num == 7) { print "\n---+---+---\n";}
|
if ($num == 4 || $num == 7) { print "\n---+---+---\n";}
|
||||||
print "$char";
|
print "$char";
|
||||||
if ($num % 3 > 0) { print "!" }
|
if ($num % 3 > 0) { print "!" }
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ function input() {
|
|||||||
async function askYesOrNo(question) {
|
async function askYesOrNo(question) {
|
||||||
while (1) {
|
while (1) {
|
||||||
print(question);
|
print(question);
|
||||||
const str = await input();
|
const str = (await input()).toUpperCase();
|
||||||
if (str == "YES") {
|
if (str === "YES") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (str == "NO") {
|
else if (str === "NO") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
118
94_War/ruby/war.rb
Normal file
118
94_War/ruby/war.rb
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# reinterpreted from BASIC by stephan.com
|
||||||
|
class War
|
||||||
|
class Card
|
||||||
|
class CardError < StandardError; end
|
||||||
|
|
||||||
|
SUITS = %i[spades hearts clubs diamonds].freeze
|
||||||
|
PIPS = %i[ace deuce trey four five six seven eight nine ten jack king queen].freeze
|
||||||
|
CARDS = SUITS.product(PIPS).freeze
|
||||||
|
VALUES = PIPS.zip(1..13).to_h.freeze
|
||||||
|
|
||||||
|
attr_reader :value
|
||||||
|
|
||||||
|
def initialize(suit, pip)
|
||||||
|
@suit = suit
|
||||||
|
@pip = pip
|
||||||
|
raise CardError, 'invalid suit' unless SUITS.include? @suit
|
||||||
|
raise CardError, 'invalid pip' unless PIPS.include? @pip
|
||||||
|
|
||||||
|
@value = VALUES[pip]
|
||||||
|
end
|
||||||
|
|
||||||
|
def <=>(other)
|
||||||
|
@value <=> other.value
|
||||||
|
end
|
||||||
|
|
||||||
|
def >(other)
|
||||||
|
@value > other.value
|
||||||
|
end
|
||||||
|
|
||||||
|
def <(other)
|
||||||
|
@value < other.value
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
"the #{@pip} of #{@suit}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.shuffle
|
||||||
|
CARDS.map { |suit, pip| new(suit, pip) }.shuffle
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@your_score = 0
|
||||||
|
@computer_score = 0
|
||||||
|
@your_deck = Card.shuffle
|
||||||
|
@computer_deck = Card.shuffle
|
||||||
|
end
|
||||||
|
|
||||||
|
def play
|
||||||
|
intro
|
||||||
|
|
||||||
|
loop do
|
||||||
|
puts "\nYou: #{@your_score} Computer: #{@computer_score}"
|
||||||
|
round @your_deck.shift, @computer_deck.shift
|
||||||
|
break if empty?
|
||||||
|
|
||||||
|
puts 'Do you want to continue?'
|
||||||
|
break unless yesno
|
||||||
|
end
|
||||||
|
|
||||||
|
outro
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def round(your_card, computer_card)
|
||||||
|
puts "You: #{your_card} vs Computer: #{computer_card}"
|
||||||
|
return puts 'Tie. No score change.' if your_card == computer_card
|
||||||
|
|
||||||
|
if computer_card > your_card
|
||||||
|
puts "Computer wins with #{computer_card}"
|
||||||
|
@computer_score += 1
|
||||||
|
else
|
||||||
|
puts "You win with #{your_card}"
|
||||||
|
@your_score += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def yesno
|
||||||
|
loop do
|
||||||
|
wants = gets.strip
|
||||||
|
return true if wants.downcase == 'yes'
|
||||||
|
return false if wants.downcase == 'no'
|
||||||
|
|
||||||
|
puts 'Yes or no, please.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def intro
|
||||||
|
puts 'War'.center(80)
|
||||||
|
puts 'stephan.com'.center(80)
|
||||||
|
puts
|
||||||
|
puts 'This is the card game of war.'
|
||||||
|
puts 'Do you want directions'
|
||||||
|
directions if yesno
|
||||||
|
end
|
||||||
|
|
||||||
|
def directions
|
||||||
|
puts 'The computer gives you and it a \'card\'. The higher card'
|
||||||
|
puts '(numerically) wins. The game ends when you choose not to'
|
||||||
|
puts 'continue or when you have finished the pack.'
|
||||||
|
puts
|
||||||
|
end
|
||||||
|
|
||||||
|
def outro
|
||||||
|
puts "We've run out of cards" if empty?
|
||||||
|
puts "Final score:\nYou: #{@your_score}\nComputer: #{@computer_score}"
|
||||||
|
puts 'Thanks for playing!'
|
||||||
|
end
|
||||||
|
|
||||||
|
def empty?
|
||||||
|
@your_deck.empty? || @computer_deck.empty?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
War.new.play
|
||||||
@@ -3,83 +3,345 @@
|
|||||||
// Converted from BASIC to Javascript by Oscar Toledo G. (nanochess)
|
// Converted from BASIC to Javascript by Oscar Toledo G. (nanochess)
|
||||||
//
|
//
|
||||||
|
|
||||||
function print(str)
|
/**
|
||||||
{
|
* Print given string to the end of the "output" element.
|
||||||
|
* @param str
|
||||||
|
*/
|
||||||
|
function print(str) {
|
||||||
document.getElementById("output").appendChild(document.createTextNode(str));
|
document.getElementById("output").appendChild(document.createTextNode(str));
|
||||||
}
|
}
|
||||||
|
|
||||||
function input()
|
/**
|
||||||
{
|
* Obtain user input
|
||||||
var input_element;
|
* @returns {Promise<String>}
|
||||||
var input_str;
|
*/
|
||||||
|
function input() {
|
||||||
return new Promise(function (resolve) {
|
return new Promise(function (resolve) {
|
||||||
input_element = document.createElement("INPUT");
|
const input_element = document.createElement("INPUT");
|
||||||
|
|
||||||
print("? ");
|
print("? ");
|
||||||
input_element.setAttribute("type", "text");
|
input_element.setAttribute("type", "text");
|
||||||
input_element.setAttribute("length", "50");
|
input_element.setAttribute("length", "50");
|
||||||
document.getElementById("output").appendChild(input_element);
|
document.getElementById("output").appendChild(input_element);
|
||||||
input_element.focus();
|
input_element.focus();
|
||||||
input_str = undefined;
|
input_element.addEventListener("keydown", function (event) {
|
||||||
input_element.addEventListener("keydown", function (event) {
|
if (event.keyCode === 13) {
|
||||||
if (event.keyCode == 13) {
|
const input_str = input_element.value;
|
||||||
input_str = input_element.value;
|
document.getElementById("output").removeChild(input_element);
|
||||||
document.getElementById("output").removeChild(input_element);
|
print(input_str);
|
||||||
print(input_str);
|
print("\n");
|
||||||
print("\n");
|
resolve(input_str);
|
||||||
resolve(input_str);
|
}
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function tab(space)
|
/**
|
||||||
{
|
* Create a string consisting of the given number of spaces
|
||||||
var str = "";
|
* @param spaceCount
|
||||||
while (space-- > 0)
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function tab(spaceCount) {
|
||||||
|
let str = "";
|
||||||
|
while (spaceCount-- > 0)
|
||||||
str += " ";
|
str += " ";
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fna(arg) {
|
const MONTHS_PER_YEAR = 12;
|
||||||
return Math.floor(arg / 4);
|
const DAYS_PER_COMMON_YEAR = 365;
|
||||||
|
const DAYS_PER_IDEALISED_MONTH = 30;
|
||||||
|
const MAXIMUM_DAYS_PER_MONTH = 31;
|
||||||
|
// In a common (non-leap) year the day of the week for the first of each month moves by the following amounts.
|
||||||
|
const COMMON_YEAR_MONTH_OFFSET = [0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date representation.
|
||||||
|
*/
|
||||||
|
class DateStruct {
|
||||||
|
#year;
|
||||||
|
#month;
|
||||||
|
#day;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a DateStruct
|
||||||
|
* @param {number} year
|
||||||
|
* @param {number} month
|
||||||
|
* @param {number} day
|
||||||
|
*/
|
||||||
|
constructor(year, month, day) {
|
||||||
|
this.#year = year;
|
||||||
|
this.#month = month;
|
||||||
|
this.#day = day;
|
||||||
|
}
|
||||||
|
|
||||||
|
get year() {
|
||||||
|
return this.#year;
|
||||||
|
}
|
||||||
|
|
||||||
|
get month() {
|
||||||
|
return this.#month;
|
||||||
|
}
|
||||||
|
|
||||||
|
get day() {
|
||||||
|
return this.#day;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the date could be a Gregorian date.
|
||||||
|
* Be aware the Gregorian calendar was not introduced in all places at once,
|
||||||
|
* see https://en.wikipedia.org/wiki/Gregorian_calendar
|
||||||
|
* @returns {boolean} true if date could be Gregorian; otherwise false.
|
||||||
|
*/
|
||||||
|
isGregorianDate() {
|
||||||
|
let result = false;
|
||||||
|
if (this.#year > 1582) {
|
||||||
|
result = true;
|
||||||
|
} else if (this.#year === 1582) {
|
||||||
|
if (this.#month > 10) {
|
||||||
|
result = true;
|
||||||
|
} else if (this.#month === 10 && this.#day >= 15) {
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The following performs a hash on the day parts which guarantees that
|
||||||
|
* 1. different days will return different numbers
|
||||||
|
* 2. the numbers returned are ordered.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
getNormalisedDay() {
|
||||||
|
return (this.year * MONTHS_PER_YEAR + this.month) * MAXIMUM_DAYS_PER_MONTH + this.day;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the day of the week.
|
||||||
|
* This calculation returns a number between 1 and 7 where Sunday=1, Monday=2, ..., Saturday=7.
|
||||||
|
* @returns {number} Value between 1 and 7 representing Sunday to Saturday.
|
||||||
|
*/
|
||||||
|
getDayOfWeek() {
|
||||||
|
// Calculate an offset based on the century part of the year.
|
||||||
|
const centuriesSince1500 = Math.floor((this.year - 1500) / 100);
|
||||||
|
let centuryOffset = centuriesSince1500 * 5 + (centuriesSince1500 + 3) / 4;
|
||||||
|
centuryOffset = Math.floor(centuryOffset % 7);
|
||||||
|
|
||||||
|
// Calculate an offset based on the shortened two digit year.
|
||||||
|
// January 1st moves forward by approximately 1.25 days per year
|
||||||
|
const yearInCentury = this.year % 100;
|
||||||
|
const yearInCenturyOffsets = yearInCentury / 4 + yearInCentury;
|
||||||
|
|
||||||
|
// combine offsets with day and month
|
||||||
|
let dayOfWeek = centuryOffset + yearInCenturyOffsets + this.day + COMMON_YEAR_MONTH_OFFSET[this.month - 1];
|
||||||
|
|
||||||
|
dayOfWeek = Math.floor(dayOfWeek % 7) + 1;
|
||||||
|
if (this.month <= 2 && this.isLeapYear()) {
|
||||||
|
dayOfWeek--;
|
||||||
|
}
|
||||||
|
if (dayOfWeek === 0) {
|
||||||
|
dayOfWeek = 7;
|
||||||
|
}
|
||||||
|
return dayOfWeek;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the given year is a leap year.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isLeapYear() {
|
||||||
|
if ((this.year % 4) !== 0) {
|
||||||
|
return false;
|
||||||
|
} else if ((this.year % 100) !== 0) {
|
||||||
|
return true;
|
||||||
|
} else if ((this.year % 400) !== 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a US formatted date, i.e. Month/Day/Year.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
toString() {
|
||||||
|
return this.#month + "/" + this.#day + "/" + this.#year;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fnb(arg) {
|
/**
|
||||||
return Math.floor(arg / 7);
|
* Duration representation.
|
||||||
}
|
* Note: this class only handles positive durations well
|
||||||
|
*/
|
||||||
|
class Duration {
|
||||||
|
#years;
|
||||||
|
#months;
|
||||||
|
#days;
|
||||||
|
|
||||||
var t = [, 0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5];
|
/**
|
||||||
|
* Build a Duration
|
||||||
var k5;
|
* @param {number} years
|
||||||
var k6;
|
* @param {number} months
|
||||||
var k7;
|
* @param {number} days
|
||||||
|
*/
|
||||||
function time_spent(f, a8)
|
constructor(years, months, days) {
|
||||||
{
|
this.#years = years;
|
||||||
k1 = Math.floor(f * a8);
|
this.#months = months;
|
||||||
i5 = Math.floor(k1 / 365);
|
this.#days = days;
|
||||||
k1 -= i5 * 365;
|
this.#fixRanges();
|
||||||
i6 = Math.floor(k1 / 30);
|
|
||||||
i7 = k1 - (i6 * 30);
|
|
||||||
k5 -= i5;
|
|
||||||
k6 -= i6;
|
|
||||||
k7 -= i7;
|
|
||||||
if (k7 < 0) {
|
|
||||||
k7 += 30;
|
|
||||||
k6--;
|
|
||||||
}
|
}
|
||||||
if (k6 <= 0) {
|
|
||||||
k6 += 12;
|
get years() {
|
||||||
k5--;
|
return this.#years;
|
||||||
|
}
|
||||||
|
|
||||||
|
get months() {
|
||||||
|
return this.#months;
|
||||||
|
}
|
||||||
|
|
||||||
|
get days() {
|
||||||
|
return this.#days;
|
||||||
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
return new Duration(this.#years, this.#months, this.#days);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjust Duration by removing years, months and days from supplied Duration.
|
||||||
|
* This is a naive calculation which assumes all months are 30 days.
|
||||||
|
* @param {Duration} timeToRemove
|
||||||
|
*/
|
||||||
|
remove(timeToRemove) {
|
||||||
|
this.#years -= timeToRemove.years;
|
||||||
|
this.#months -= timeToRemove.months;
|
||||||
|
this.#days -= timeToRemove.days;
|
||||||
|
this.#fixRanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move days and months into expected range.
|
||||||
|
*/
|
||||||
|
#fixRanges() {
|
||||||
|
if (this.#days < 0) {
|
||||||
|
this.#days += DAYS_PER_IDEALISED_MONTH;
|
||||||
|
this.#months--;
|
||||||
|
}
|
||||||
|
if (this.#months < 0) {
|
||||||
|
this.#months += MONTHS_PER_YEAR;
|
||||||
|
this.#years--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes an approximation of the days covered by the duration.
|
||||||
|
* The calculation assumes all years are 365 days, months are 30 days each,
|
||||||
|
* and adds on an extra bit the more months that have passed.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
getApproximateDays() {
|
||||||
|
return (
|
||||||
|
(this.#years * DAYS_PER_COMMON_YEAR)
|
||||||
|
+ (this.#months * DAYS_PER_IDEALISED_MONTH)
|
||||||
|
+ this.#days
|
||||||
|
+ Math.floor(this.#months / 2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a formatted duration with tab separated values, i.e. Years\tMonths\tDays.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
toString() {
|
||||||
|
return this.#years + "\t" + this.#months + "\t" + this.#days;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine approximate Duration between two dates.
|
||||||
|
* This is a naive calculation which assumes all months are 30 days.
|
||||||
|
* @param {DateStruct} date1
|
||||||
|
* @param {DateStruct} date2
|
||||||
|
* @returns {Duration}
|
||||||
|
*/
|
||||||
|
static between(date1, date2) {
|
||||||
|
let years = date1.year - date2.year;
|
||||||
|
let months = date1.month - date2.month;
|
||||||
|
let days = date1.day - date2.day;
|
||||||
|
return new Duration(years, months, days);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate years, months and days as factor of days.
|
||||||
|
* This is a naive calculation which assumes all months are 30 days.
|
||||||
|
* @param dayCount Total day to convert to a duration
|
||||||
|
* @param factor Factor to apply when calculating the duration
|
||||||
|
* @returns {Duration}
|
||||||
|
*/
|
||||||
|
static fromDays(dayCount, factor) {
|
||||||
|
let totalDays = Math.floor(factor * dayCount);
|
||||||
|
const years = Math.floor(totalDays / DAYS_PER_COMMON_YEAR);
|
||||||
|
totalDays -= years * DAYS_PER_COMMON_YEAR;
|
||||||
|
const months = Math.floor(totalDays / DAYS_PER_IDEALISED_MONTH);
|
||||||
|
const days = totalDays - (months * DAYS_PER_IDEALISED_MONTH);
|
||||||
|
return new Duration(years, months, days);
|
||||||
}
|
}
|
||||||
print(i5 + "\t" + i6 + "\t" + i7 + "\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main control section
|
// Main control section
|
||||||
async function main()
|
async function main() {
|
||||||
{
|
/**
|
||||||
|
* Reads a date, and extracts the date information.
|
||||||
|
* This expects date parts to be comma separated, using US date ordering,
|
||||||
|
* i.e. Month,Day,Year.
|
||||||
|
* @returns {Promise<DateStruct>}
|
||||||
|
*/
|
||||||
|
async function inputDate() {
|
||||||
|
let dateString = await input();
|
||||||
|
const month = parseInt(dateString);
|
||||||
|
const day = parseInt(dateString.substr(dateString.indexOf(",") + 1));
|
||||||
|
const year = parseInt(dateString.substr(dateString.lastIndexOf(",") + 1));
|
||||||
|
return new DateStruct(year, month, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain text for the day of the week.
|
||||||
|
* @param {DateStruct} date
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function getDayOfWeekText(date) {
|
||||||
|
const dayOfWeek = date.getDayOfWeek();
|
||||||
|
let dayOfWeekText = "";
|
||||||
|
switch (dayOfWeek) {
|
||||||
|
case 1:
|
||||||
|
dayOfWeekText = "SUNDAY.";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
dayOfWeekText = "MONDAY.";
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
dayOfWeekText = "TUESDAY.";
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
dayOfWeekText = "WEDNESDAY.";
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
dayOfWeekText = "THURSDAY.";
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
if (date.day === 13) {
|
||||||
|
dayOfWeekText = "FRIDAY THE THIRTEENTH---BEWARE!";
|
||||||
|
} else {
|
||||||
|
dayOfWeekText = "FRIDAY.";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
dayOfWeekText = "SATURDAY.";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return dayOfWeekText;
|
||||||
|
}
|
||||||
|
|
||||||
print(tab(32) + "WEEKDAY\n");
|
print(tab(32) + "WEEKDAY\n");
|
||||||
print(tab(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n");
|
print(tab(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n");
|
||||||
print("\n");
|
print("\n");
|
||||||
@@ -89,111 +351,69 @@ async function main()
|
|||||||
print("GIVES FACTS ABOUT A DATE OF INTEREST TO YOU.\n");
|
print("GIVES FACTS ABOUT A DATE OF INTEREST TO YOU.\n");
|
||||||
print("\n");
|
print("\n");
|
||||||
print("ENTER TODAY'S DATE IN THE FORM: 3,24,1979 ");
|
print("ENTER TODAY'S DATE IN THE FORM: 3,24,1979 ");
|
||||||
str = await input();
|
const today = await inputDate();
|
||||||
m1 = parseInt(str);
|
|
||||||
d1 = parseInt(str.substr(str.indexOf(",") + 1));
|
|
||||||
y1 = parseInt(str.substr(str.lastIndexOf(",") + 1));
|
|
||||||
// This program determines the day of the week
|
// This program determines the day of the week
|
||||||
// for a date after 1582
|
// for a date after 1582
|
||||||
print("ENTER DAY OF BIRTH (OR OTHER DAY OF INTEREST)");
|
print("ENTER DAY OF BIRTH (OR OTHER DAY OF INTEREST)");
|
||||||
str = await input();
|
const dateOfBirth = await inputDate();
|
||||||
m = parseInt(str);
|
|
||||||
d = parseInt(str.substr(str.indexOf(",") + 1));
|
|
||||||
y = parseInt(str.substr(str.lastIndexOf(",") + 1));
|
|
||||||
print("\n");
|
print("\n");
|
||||||
i1 = Math.floor((y - 1500) / 100);
|
|
||||||
// Test for date before current calendar.
|
// Test for date before current calendar.
|
||||||
if (y - 1582 < 0) {
|
if (!dateOfBirth.isGregorianDate()) {
|
||||||
print("NOT PREPARED TO GIVE DAY OF WEEK PRIOR TO MDLXXXII.\n");
|
print("NOT PREPARED TO GIVE DAY OF WEEK PRIOR TO X.XV.MDLXXXII.\n");
|
||||||
} else {
|
} else {
|
||||||
a = i1 * 5 + (i1 + 3) / 4;
|
const normalisedToday = today.getNormalisedDay();
|
||||||
i2 = Math.floor(a - fnb(a) * 7);
|
const normalisedDob = dateOfBirth.getNormalisedDay();
|
||||||
y2 = Math.floor(y / 100);
|
|
||||||
y3 = Math.floor(y - y2 * 100);
|
let dayOfWeekText = getDayOfWeekText(dateOfBirth);
|
||||||
a = y3 / 4 + y3 + d + t[m] + i2;
|
if (normalisedToday < normalisedDob) {
|
||||||
b = Math.floor(a - fnb(a) * 7) + 1;
|
print(dateOfBirth + " WILL BE A " + dayOfWeekText + "\n");
|
||||||
if (m <= 2) {
|
} else if (normalisedToday === normalisedDob) {
|
||||||
if (y3 != 0) {
|
print(dateOfBirth + " IS A " + dayOfWeekText + "\n");
|
||||||
t1 = Math.floor(y - fna(y) * 4);
|
|
||||||
} else {
|
|
||||||
a = i1 - 1;
|
|
||||||
t1 = Math.floor(a - fna(a) * 4);
|
|
||||||
}
|
|
||||||
if (t1 == 0) {
|
|
||||||
if (b == 0)
|
|
||||||
b = 6;
|
|
||||||
b--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (b == 0)
|
|
||||||
b = 7;
|
|
||||||
if ((y1 * 12 + m1) * 31 + d1 < (y * 12 + m) * 31 + d) {
|
|
||||||
print(m + "/" + d + "/" + y + " WILL BE A ");
|
|
||||||
} else if ((y1 * 12 + m1) * 31 + d1 == (y * 12 + m) * 31 + d) {
|
|
||||||
print(m + "/" + d + "/" + y + " IS A ");
|
|
||||||
} else {
|
} else {
|
||||||
print(m + "/" + d + "/" + y + " WAS A ");
|
print(dateOfBirth + " WAS A " + dayOfWeekText + "\n");
|
||||||
}
|
}
|
||||||
switch (b) {
|
|
||||||
case 1: print("SUNDAY.\n"); break;
|
if (normalisedToday !== normalisedDob) {
|
||||||
case 2: print("MONDAY.\n"); break;
|
|
||||||
case 3: print("TUESDAY.\n"); break;
|
|
||||||
case 4: print("WEDNESDAY.\n"); break;
|
|
||||||
case 5: print("THURSDAY.\n"); break;
|
|
||||||
case 6:
|
|
||||||
if (d == 13) {
|
|
||||||
print("FRIDAY THE THIRTEENTH---BEWARE!\n");
|
|
||||||
} else {
|
|
||||||
print("FRIDAY.\n");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 7: print("SATURDAY.\n"); break;
|
|
||||||
}
|
|
||||||
if ((y1 * 12 + m1) * 31 + d1 != (y * 12 + m) * 31 + d) {
|
|
||||||
i5 = y1 - y;
|
|
||||||
print("\n");
|
print("\n");
|
||||||
i6 = m1 - m;
|
let differenceBetweenDates = Duration.between(today, dateOfBirth);
|
||||||
i7 = d1 - d;
|
if (differenceBetweenDates.years >= 0) {
|
||||||
if (i7 < 0) {
|
if (differenceBetweenDates.days === 0 && differenceBetweenDates.months === 0) {
|
||||||
i6--;
|
|
||||||
i7 += 30;
|
|
||||||
}
|
|
||||||
if (i6 < 0) {
|
|
||||||
i5--;
|
|
||||||
i6 += 12;
|
|
||||||
}
|
|
||||||
if (i5 >= 0) {
|
|
||||||
if (i7 == 0 && i6 == 0)
|
|
||||||
print("***HAPPY BIRTHDAY***\n");
|
print("***HAPPY BIRTHDAY***\n");
|
||||||
|
}
|
||||||
print(" \tYEARS\tMONTHS\tDAYS\n");
|
print(" \tYEARS\tMONTHS\tDAYS\n");
|
||||||
print(" \t-----\t------\t----\n");
|
print(" \t-----\t------\t----\n");
|
||||||
print("YOUR AGE (IF BIRTHDATE) \t" + i5 + "\t" + i6 + "\t" + i7 + "\n");
|
print("YOUR AGE (IF BIRTHDATE) \t" + differenceBetweenDates + "\n");
|
||||||
a8 = (i5 * 365) + (i6 * 30) + i7 + Math.floor(i6 / 2);
|
|
||||||
k5 = i5;
|
const approximateDaysBetween = differenceBetweenDates.getApproximateDays();
|
||||||
k6 = i6;
|
const unaccountedTime = differenceBetweenDates.clone();
|
||||||
k7 = i7;
|
|
||||||
// Calculate retirement date.
|
// 35% sleeping
|
||||||
e = y + 65;
|
const sleepTimeSpent = Duration.fromDays(approximateDaysBetween, 0.35);
|
||||||
// Calculate time spent in the following functions.
|
print("YOU HAVE SLEPT \t\t\t" + sleepTimeSpent + "\n");
|
||||||
print("YOU HAVE SLEPT \t\t\t");
|
unaccountedTime.remove(sleepTimeSpent);
|
||||||
time_spent(0.35, a8);
|
|
||||||
print("YOU HAVE EATEN \t\t\t");
|
// 17% eating
|
||||||
time_spent(0.17, a8);
|
const eatenTimeSpent = Duration.fromDays(approximateDaysBetween, 0.17);
|
||||||
if (k5 <= 3) {
|
print("YOU HAVE EATEN \t\t\t" + eatenTimeSpent + "\n");
|
||||||
print("YOU HAVE PLAYED \t\t\t");
|
unaccountedTime.remove(eatenTimeSpent);
|
||||||
} else if (k5 <= 9) {
|
|
||||||
print("YOU HAVE PLAYED/STUDIED \t\t");
|
// 23% working, studying or playing
|
||||||
|
const workPlayTimeSpent = Duration.fromDays(approximateDaysBetween, 0.23);
|
||||||
|
if (unaccountedTime.years <= 3) {
|
||||||
|
print("YOU HAVE PLAYED \t\t" + workPlayTimeSpent + "\n");
|
||||||
|
} else if (unaccountedTime.years <= 9) {
|
||||||
|
print("YOU HAVE PLAYED/STUDIED \t" + workPlayTimeSpent + "\n");
|
||||||
} else {
|
} else {
|
||||||
print("YOU HAVE WORKED/PLAYED \t\t");
|
print("YOU HAVE WORKED/PLAYED \t\t" + workPlayTimeSpent + "\n");
|
||||||
}
|
}
|
||||||
time_spent(0.23, a8);
|
unaccountedTime.remove(workPlayTimeSpent);
|
||||||
if (k6 == 12) {
|
|
||||||
k5++;
|
// Remaining time spent relaxing
|
||||||
k6 = 0;
|
print("YOU HAVE RELAXED \t\t" + unaccountedTime + "\n");
|
||||||
}
|
|
||||||
print("YOU HAVE RELAXED \t\t" + k5 + "\t" + k6 + "\t" + k7 + "\n");
|
const retirementYear = dateOfBirth.year + 65;
|
||||||
print("\n");
|
print("\n");
|
||||||
print(tab(16) + "*** YOU MAY RETIRE IN " + e + " ***\n");
|
print(tab(16) + "*** YOU MAY RETIRE IN " + retirementYear + " ***\n");
|
||||||
print("\n");
|
print("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||||
|
|
||||||
Conversion to [Perl](https://www.perl.org/)
|
Conversion to [Perl](https://www.perl.org/)
|
||||||
|
|
||||||
|
I have replaced the manual date logic with Perl built-ins to the extent
|
||||||
|
possible. Unfortunately the kind of date math involved in the "time
|
||||||
|
spent doing ..." functionality is not well-defined, so I have been
|
||||||
|
forced to retain the original logic here. Sigh.
|
||||||
|
|
||||||
|
You can use any punctuation character you please in the date
|
||||||
|
input. So something like 2/29/2020 is perfectly acceptable.
|
||||||
|
|
||||||
|
It would also have been nice to produce a localized version that
|
||||||
|
supports day/month/year or year-month-day input, but that didn't happen.
|
||||||
|
|
||||||
|
Also nice would have been language-specific output -- especially if it
|
||||||
|
could have accommodated regional differences in which day of the week or
|
||||||
|
month is unlucky.
|
||||||
|
|
||||||
|
Tom Wyant
|
||||||
|
|||||||
249
95_Weekday/perl/weekday.pl
Executable file
249
95_Weekday/perl/weekday.pl
Executable file
@@ -0,0 +1,249 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
|
||||||
|
use 5.010; # To get 'state' and 'say'
|
||||||
|
|
||||||
|
use strict; # Require explicit declaration of variables
|
||||||
|
use warnings; # Enable optional compiler warnings
|
||||||
|
|
||||||
|
use English; # Use more friendly names for Perl's magic variables
|
||||||
|
use Term::ReadLine; # Prompt and return user input
|
||||||
|
use Time::Local qw{ timelocal }; # date-time to epoch
|
||||||
|
# FIXME timelocal() is too smart for its own good in the interpretation
|
||||||
|
# of years, and caused a bunch of Y2020 problems in Perl code that used
|
||||||
|
# it. I believe that this script avoids these problems (which only occur
|
||||||
|
# if the year is less than 1000), but it is probably safer in general to
|
||||||
|
# use timelocal_modern() or timelocal_posix(). These are also exported
|
||||||
|
# by Time::Local, but only by versions 1.28 and 1.30 respectively. This
|
||||||
|
# means that they only come (by default) with Perl 5.30 and 5.34
|
||||||
|
# respectively. Now, Time::Local is a dual-life module, meaning it can
|
||||||
|
# be upgraded from the version packaged with older Perls. But I did not
|
||||||
|
# want to assume that it HAD been upgraded. Caveat coder.
|
||||||
|
use Time::Piece; # O-O epoch to date-time, plus formatting
|
||||||
|
|
||||||
|
our $VERSION = '0.000_01';
|
||||||
|
|
||||||
|
print <<'EOD';
|
||||||
|
|
||||||
|
WEEKDAY
|
||||||
|
Creative Computing Morristown, New Jersey
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
WEEKDAY is a computer demonstration that
|
||||||
|
gives facts about a date of interest to you.
|
||||||
|
|
||||||
|
EOD
|
||||||
|
|
||||||
|
my $now = localtime;
|
||||||
|
my $default_date = join ',', map { $now->$_() } qw{ mon mday year };
|
||||||
|
|
||||||
|
my $today = get_date(
|
||||||
|
"Enter today's date in the form month,day,year (default: $default_date): ",
|
||||||
|
"Please enter month,day,year or return for default\n",
|
||||||
|
$default_date,
|
||||||
|
);
|
||||||
|
|
||||||
|
my $birthday = get_date(
|
||||||
|
'Ender day of birth (or other day of interest): ',
|
||||||
|
"Please enter month,day,year\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
say '';
|
||||||
|
printf "%d/%d/%d %s a %s\n", $birthday->mon, $birthday->mday,
|
||||||
|
$birthday->year, tense( $today, $birthday),
|
||||||
|
( $birthday->mday == 13 && $birthday->wday == 6 ) ?
|
||||||
|
$birthday->fullday . ' the thirteenth --- Beware!' :
|
||||||
|
$birthday->fullday . '.';
|
||||||
|
|
||||||
|
if ( $birthday->epoch <= $today->epoch ) {
|
||||||
|
|
||||||
|
say '*** Happy Birthday! ***'
|
||||||
|
if $birthday->mon == $today->mon &&
|
||||||
|
$birthday->mday == $today->mday;
|
||||||
|
|
||||||
|
print <<'EOD';
|
||||||
|
Years Months Days
|
||||||
|
----- ------ ----
|
||||||
|
EOD
|
||||||
|
|
||||||
|
my @delta = map { $today->$_() - $birthday->$_() } qw{ year mon mday };
|
||||||
|
if ( $delta[2] < 0 ) {
|
||||||
|
$delta[2] += 30;
|
||||||
|
$delta[1] -= 1;
|
||||||
|
}
|
||||||
|
if ( $delta[1] < 0 ) {
|
||||||
|
$delta[1] += 12;
|
||||||
|
$delta[0] -= 1;
|
||||||
|
}
|
||||||
|
my @residue = @delta;
|
||||||
|
|
||||||
|
my $delta_days = 365 * $delta[0] + 30 * $delta[1] + $delta[2];
|
||||||
|
|
||||||
|
display_ymd( 'Your age (if birthdate)', compute_ymd( $delta_days ) );
|
||||||
|
display_ymd( 'You have slept', compute_ymd( $delta_days, 0.35,
|
||||||
|
\@residue ) );
|
||||||
|
display_ymd( 'You have eaten', compute_ymd( $delta_days, 0.17,
|
||||||
|
\@residue ) );
|
||||||
|
display_ymd(
|
||||||
|
$residue[0] > 9 ? 'You have worked/played' :
|
||||||
|
$residue[0] > 3 ? 'You have played/studied' :
|
||||||
|
'You have played',
|
||||||
|
compute_ymd( $delta_days, 0.23,
|
||||||
|
\@residue ) );
|
||||||
|
display_ymd( 'You have relaxed', \@residue );
|
||||||
|
|
||||||
|
say '';
|
||||||
|
say "\t\t*** You may retire in @{[ $birthday->year + 65 ]} ***";
|
||||||
|
}
|
||||||
|
|
||||||
|
say '';
|
||||||
|
|
||||||
|
sub compute_ymd {
|
||||||
|
my ( $delta_days, $fract, $residue ) = @ARG;
|
||||||
|
my $days = defined $fract ? int ( $delta_days * $fract ) : $delta_days;
|
||||||
|
my $years = int( $days / 365 );
|
||||||
|
$days -= $years * 365;
|
||||||
|
my $months = int( $days / 30 );
|
||||||
|
$days -= $months * 30;
|
||||||
|
|
||||||
|
if ( $residue ) {
|
||||||
|
$residue->[2] -= $days;
|
||||||
|
if ( $residue->[2] < 0 ) {
|
||||||
|
$residue->[2] += 30;
|
||||||
|
$residue->[1] -= 1;
|
||||||
|
}
|
||||||
|
$residue->[1] -= $months;
|
||||||
|
if ( $residue->[1] < 0 ) {
|
||||||
|
$residue->[1] += 12;
|
||||||
|
$residue->[0] -= 1;
|
||||||
|
}
|
||||||
|
$residue->[0] -= $years;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ $years, $months, $days ];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub display_ymd {
|
||||||
|
my ( $label, $ymd ) = @ARG;
|
||||||
|
printf "%-24s%4d%6d%8d\n", $label, @{ $ymd };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_date {
|
||||||
|
my ( $prompt, $warning, $default ) = @ARG;
|
||||||
|
my ( $month, $day, $year ) = split qr< [[:punct:]] >smx, get_input(
|
||||||
|
$prompt,
|
||||||
|
sub {
|
||||||
|
return 0 unless m/ \A (?: [0-9]+ [[:punct:]] ){2} ( [0-9]+ ) \z /smx;
|
||||||
|
return 1 if $1 >= 1582;
|
||||||
|
warn "Not prepared to give day of week prior to MDLXXXII.\n";
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
$warning,
|
||||||
|
$default,
|
||||||
|
);
|
||||||
|
return localtime timelocal( 0, 0, 0, $day, $month - 1, $year );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub tense {
|
||||||
|
my ( $today, $birthday ) = @ARG;
|
||||||
|
my $cmp = $birthday->epoch <=> $today->epoch
|
||||||
|
or return 'is';
|
||||||
|
return $cmp < 0 ? 'was' : 'will be';
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get input from the user. The arguments are:
|
||||||
|
# * The prompt
|
||||||
|
# * A reference to validation code. This code receives the response in
|
||||||
|
# $ARG and returns true for a valid response.
|
||||||
|
# * A warning to print if the response is not valid. This must end in a
|
||||||
|
# return.
|
||||||
|
# * A default to return if the user simply presses <return>.
|
||||||
|
# The first valid response is returned. An end-of-file terminates the
|
||||||
|
# script.
|
||||||
|
sub get_input {
|
||||||
|
my ( $prompt, $validate, $warning, $default ) = @ARG;
|
||||||
|
|
||||||
|
# If no validator is passed, default to one that always returns
|
||||||
|
# true.
|
||||||
|
$validate ||= sub { 1 };
|
||||||
|
|
||||||
|
# Create the readline object. The 'state' causes the variable to be
|
||||||
|
# initialized only once, no matter how many times this subroutine is
|
||||||
|
# called. The do { ... } is a compound statement used because we
|
||||||
|
# need to tweak the created object before we store it.
|
||||||
|
state $term = do {
|
||||||
|
my $obj = Term::ReadLine->new( 'reverse' );
|
||||||
|
$obj->ornaments( 0 );
|
||||||
|
$obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
while ( 1 ) { # Iterate indefinitely
|
||||||
|
|
||||||
|
# Read the input into the topic variable, localized to prevent
|
||||||
|
# Spooky Action at a Distance. We exit on undef, which signals
|
||||||
|
# end-of-file.
|
||||||
|
exit unless defined( local $ARG = $term->readline( $prompt ) );
|
||||||
|
|
||||||
|
# Return the default if it exists AND we got an empty line
|
||||||
|
return $default if defined( $default ) && $ARG eq '';
|
||||||
|
|
||||||
|
# Return the input if it is valid.
|
||||||
|
return $ARG if $validate->();
|
||||||
|
|
||||||
|
# Issue the warning, and go around the merry-go-round again.
|
||||||
|
warn $warning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__END__
|
||||||
|
|
||||||
|
=head1 TITLE
|
||||||
|
|
||||||
|
weekday - Play the game 'Weekday' from Basic Computer Games
|
||||||
|
|
||||||
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
weekday.pl
|
||||||
|
|
||||||
|
=head1 DETAILS
|
||||||
|
|
||||||
|
This Perl script is a port of weekday.bas, which is the 95th entry in
|
||||||
|
Basic Computer Games.
|
||||||
|
|
||||||
|
I have replaced the manual date logic with Perl built-ins to the extent
|
||||||
|
possible. Unfortunately the kind of date math involved in the "time
|
||||||
|
spent doing ..." functionality is not well-defined, so I have been
|
||||||
|
forced to retain the original logic here. Sigh.
|
||||||
|
|
||||||
|
You can use any punctuation character you please in the date
|
||||||
|
input. So something like 2/29/2020 is perfectly acceptable.
|
||||||
|
|
||||||
|
It would also have been nice to produce a localized version that
|
||||||
|
supports day/month/year or year-month-day input, but that didn't happen.
|
||||||
|
|
||||||
|
Also nice would have been language-specific output -- especially if it
|
||||||
|
could have accommodated regional differences in which day of the week or
|
||||||
|
month is unlucky.
|
||||||
|
|
||||||
|
=head1 PORTED BY
|
||||||
|
|
||||||
|
Thomas R. Wyant, III F<wyant at cpan dot org>
|
||||||
|
|
||||||
|
=head1 COPYRIGHT AND LICENSE
|
||||||
|
|
||||||
|
Copyright (C) 2022 by Thomas R. Wyant, III
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify it
|
||||||
|
under the same terms as Perl 5.10.0. For more details, see the Artistic
|
||||||
|
License 1.0 at
|
||||||
|
L<https://www.perlfoundation.org/artistic-license-10.html>, and/or the
|
||||||
|
Gnu GPL at L<http://www.gnu.org/licenses/old-licenses/gpl-1.0.txt>.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but
|
||||||
|
without any warranty; without even the implied warranty of
|
||||||
|
merchantability or fitness for a particular purpose.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
# ex: set expandtab tabstop=4 textwidth=72 :
|
||||||
Reference in New Issue
Block a user