From 9b471073b0dcd88612c10acbf1a4bfc3f04c6075 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Fri, 21 Oct 2022 21:48:58 +1100 Subject: [PATCH 001/141] CSHARP-53 Create program sturcture --- 53_King/csharp/Game.cs | 28 ++++++++++++ 53_King/csharp/King.csproj | 8 ++++ 53_King/csharp/Program.cs | 6 +++ .../csharp/Resources/Instructions_Prompt.txt | 1 + .../csharp/Resources/Instructions_Text.txt | 17 +++++++ 53_King/csharp/Resources/Resource.cs | 45 +++++++++++++++++++ 53_King/csharp/Resources/Title.txt | 5 +++ 7 files changed, 110 insertions(+) create mode 100644 53_King/csharp/Game.cs create mode 100644 53_King/csharp/Program.cs create mode 100644 53_King/csharp/Resources/Instructions_Prompt.txt create mode 100644 53_King/csharp/Resources/Instructions_Text.txt create mode 100644 53_King/csharp/Resources/Resource.cs create mode 100644 53_King/csharp/Resources/Title.txt diff --git a/53_King/csharp/Game.cs b/53_King/csharp/Game.cs new file mode 100644 index 00000000..d357bdf3 --- /dev/null +++ b/53_King/csharp/Game.cs @@ -0,0 +1,28 @@ +namespace King; + +internal class Game +{ + const int TermOfOffice = 8; + + private readonly IReadWrite _io; + private readonly IRandom _random; + + public Game(IReadWrite io, IRandom random) + { + _io = io; + _random = random; + } + + public void Play() + { + _io.Write(Resource.Title); + + var response = _io.ReadString(Resource.Instructions_Prompt).ToUpper(); + if (!response.StartsWith('N')) + { + _io.Write(Resource.Instructions_Text(TermOfOffice)); + } + + _io.WriteLine(); + } +} \ No newline at end of file diff --git a/53_King/csharp/King.csproj b/53_King/csharp/King.csproj index d3fe4757..3870320c 100644 --- a/53_King/csharp/King.csproj +++ b/53_King/csharp/King.csproj @@ -6,4 +6,12 @@ enable enable + + + + + + + + diff --git a/53_King/csharp/Program.cs b/53_King/csharp/Program.cs new file mode 100644 index 00000000..6eb4c50e --- /dev/null +++ b/53_King/csharp/Program.cs @@ -0,0 +1,6 @@ +global using Games.Common.IO; +global using Games.Common.Randomness; +global using King.Resources; +using King; + +new Game(new ConsoleIO(), new RandomNumberGenerator()).Play(); diff --git a/53_King/csharp/Resources/Instructions_Prompt.txt b/53_King/csharp/Resources/Instructions_Prompt.txt new file mode 100644 index 00000000..0d311b60 --- /dev/null +++ b/53_King/csharp/Resources/Instructions_Prompt.txt @@ -0,0 +1 @@ +Do you want instructions \ No newline at end of file diff --git a/53_King/csharp/Resources/Instructions_Text.txt b/53_King/csharp/Resources/Instructions_Text.txt new file mode 100644 index 00000000..c576123b --- /dev/null +++ b/53_King/csharp/Resources/Instructions_Text.txt @@ -0,0 +1,17 @@ + + + +Congratulations! You've just been elected Premier of Setats +Detinu, a small communist island 30 by 70 miles long. Your +job is to decide upon the country's budget and distribute +money to your countrymen from the communal treasury. +The money system is rallods, and each person needs 100 +rallods per year to survive. Your country's income comes +from farm produce and tourists visiting your magnificent +forests, hunting, fishing, etc. Half your land if farm land +which also has an excellent mineral content and may be sold +to foreign industry (strip mining) who import and support +their own workers. Crops cost between 10 and 15 rallods per +square mile to plant. +Your goal is to complete your {0} year term of office. +Good luck! diff --git a/53_King/csharp/Resources/Resource.cs b/53_King/csharp/Resources/Resource.cs new file mode 100644 index 00000000..f8ddfd3f --- /dev/null +++ b/53_King/csharp/Resources/Resource.cs @@ -0,0 +1,45 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace King.Resources; + +internal static class Resource +{ + public static Stream Title => GetStream(); + + public static string Instructions_Prompt => GetString(); + public static string Instructions_Text(int years) => string.Format(GetString(), years); + + internal static class Formats + { + public static string Player => GetString(); + public static string YouLose => GetString(); + } + + internal static class Prompts + { + public static string WantInstructions => GetString(); + public static string HowManyPlayers => GetString(); + public static string HowManyRows => GetString(); + public static string HowManyColumns => GetString(); + public static string TooManyColumns => GetString(); + } + + internal static class Strings + { + public static string TooManyColumns => GetString(); + public static string TooManyRows => GetString(); + } + + private static string GetString([CallerMemberName] string? name = null) + { + using var stream = GetStream(name); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + + private static Stream GetStream([CallerMemberName] string? name = null) => + Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(Resource).Namespace}.{name}.txt") + ?? throw new Exception($"Could not find embedded resource stream '{name}'."); +} \ No newline at end of file diff --git a/53_King/csharp/Resources/Title.txt b/53_King/csharp/Resources/Title.txt new file mode 100644 index 00000000..f9f7854d --- /dev/null +++ b/53_King/csharp/Resources/Title.txt @@ -0,0 +1,5 @@ + King + Creative Computing Morristown, New Jersey + + + From da0f2d13ab444be03485c4d53b57d21257c27356 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Sat, 22 Oct 2022 15:46:44 +1100 Subject: [PATCH 002/141] Add reign initizalization --- 53_King/csharp/Country.cs | 34 +++++++++++++++ 53_King/csharp/Game.cs | 26 +++++++++-- 53_King/csharp/IOExtensions.cs | 43 +++++++++++++++++++ 53_King/csharp/Reign.cs | 23 ++++++++++ ...ions_Prompt.txt => InstructionsPrompt.txt} | 0 ...ructions_Text.txt => InstructionsText.txt} | 0 53_King/csharp/Resources/Resource.cs | 12 +++++- .../Resources/SavedCountrymenPrompt.txt | 1 + 53_King/csharp/Resources/SavedLandError.txt | 2 + 53_King/csharp/Resources/SavedLandPrompt.txt | 1 + .../csharp/Resources/SavedTreasuryPrompt.txt | 1 + .../csharp/Resources/SavedWorkersPrompt.txt | 1 + 53_King/csharp/Resources/SavedYearsError.txt | 1 + 53_King/csharp/Resources/SavedYearsPrompt.txt | 1 + 14 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 53_King/csharp/Country.cs create mode 100644 53_King/csharp/IOExtensions.cs create mode 100644 53_King/csharp/Reign.cs rename 53_King/csharp/Resources/{Instructions_Prompt.txt => InstructionsPrompt.txt} (100%) rename 53_King/csharp/Resources/{Instructions_Text.txt => InstructionsText.txt} (100%) create mode 100644 53_King/csharp/Resources/SavedCountrymenPrompt.txt create mode 100644 53_King/csharp/Resources/SavedLandError.txt create mode 100644 53_King/csharp/Resources/SavedLandPrompt.txt create mode 100644 53_King/csharp/Resources/SavedTreasuryPrompt.txt create mode 100644 53_King/csharp/Resources/SavedWorkersPrompt.txt create mode 100644 53_King/csharp/Resources/SavedYearsError.txt create mode 100644 53_King/csharp/Resources/SavedYearsPrompt.txt diff --git a/53_King/csharp/Country.cs b/53_King/csharp/Country.cs new file mode 100644 index 00000000..1e94e821 --- /dev/null +++ b/53_King/csharp/Country.cs @@ -0,0 +1,34 @@ +namespace King; + +internal class Country +{ + private readonly IRandom _random; + private float _rallods; + private float _countrymen; + private float _foreigners; + private float _land; + private float _plantingCost; + private float _landValue; + + public Country(IRandom random) + : this( + random, + (int)(60000 + random.NextFloat(1000) - random.NextFloat(1000)), + (int)(500 + random.NextFloat(10) - random.NextFloat(10)), + 0, + 2000) + { + } + + public Country(IRandom random, float rallods, float countrymen, float foreigners, float land) + { + _random = random; + _rallods = rallods; + _countrymen = countrymen; + _foreigners = foreigners; + _land = land; + + _plantingCost = random.Next(10, 15); + _landValue = random.Next(95, 105); + } +} diff --git a/53_King/csharp/Game.cs b/53_King/csharp/Game.cs index d357bdf3..c8a44a8c 100644 --- a/53_King/csharp/Game.cs +++ b/53_King/csharp/Game.cs @@ -17,12 +17,30 @@ internal class Game { _io.Write(Resource.Title); - var response = _io.ReadString(Resource.Instructions_Prompt).ToUpper(); - if (!response.StartsWith('N')) + if (SetUpReign() is Reign reign) { - _io.Write(Resource.Instructions_Text(TermOfOffice)); + _io.Write(reign); } _io.WriteLine(); + _io.WriteLine(); } -} \ No newline at end of file + + private Reign? SetUpReign() + { + var response = _io.ReadString(Resource.InstructionsPrompt).ToUpper(); + + if (response.Equals("Again", StringComparison.InvariantCultureIgnoreCase)) + { + return _io.TryReadGameData(_random, out var reign) ? reign : null; + } + + if (!response.StartsWith("N", StringComparison.InvariantCultureIgnoreCase)) + { + _io.Write(Resource.InstructionsText(TermOfOffice)); + } + + _io.WriteLine(); + return new Reign(_io, _random); + } +} diff --git a/53_King/csharp/IOExtensions.cs b/53_King/csharp/IOExtensions.cs new file mode 100644 index 00000000..183621ca --- /dev/null +++ b/53_King/csharp/IOExtensions.cs @@ -0,0 +1,43 @@ +using System.Diagnostics.CodeAnalysis; +using static King.Resources.Resource; + +namespace King; + +internal static class IOExtensions +{ + internal static bool TryReadGameData(this IReadWrite io, IRandom random, [NotNullWhen(true)] out Reign? reign) + { + if (io.TryReadValue(SavedYearsPrompt, v => v < Reign.MaxTerm, SavedYearsError(Reign.MaxTerm), out var years) && + io.TryReadValue(SavedTreasuryPrompt, out var rallods) && + io.TryReadValue(SavedCountrymenPrompt, out var countrymen) && + io.TryReadValue(SavedWorkersPrompt, out var workers) && + io.TryReadValue(SavedLandPrompt, v => v is > 1000 and <= 2000, SavedLandError, out var land)) + { + reign = new Reign(io, new Country(random, rallods, countrymen, workers, land), years + 1); + return true; + } + + reign = default; + return false; + } + + private static bool TryReadValue(this IReadWrite io, string prompt, out float value) + => io.TryReadValue(prompt, _ => true, "", out value); + + private static bool TryReadValue( + this IReadWrite io, + string prompt, + Predicate isValid, + string error, + out float value) + { + while (true) + { + value = io.ReadNumber(prompt); + if (value < 0) { return false; } + if (isValid(value)) { return true; } + + io.Write(error); + } + } +} \ No newline at end of file diff --git a/53_King/csharp/Reign.cs b/53_King/csharp/Reign.cs new file mode 100644 index 00000000..4e6c45c5 --- /dev/null +++ b/53_King/csharp/Reign.cs @@ -0,0 +1,23 @@ +namespace King; + +internal class Reign +{ + public const int MaxTerm = 8; + + private readonly IReadWrite _io; + private readonly Country _country; + private readonly float _year; + + public Reign(IReadWrite io, IRandom random) + : this(io, new Country(random), 0) + { + + } + + public Reign(IReadWrite io, Country country, float year) + { + _io = io; + _country = country; + _year = year; + } +} diff --git a/53_King/csharp/Resources/Instructions_Prompt.txt b/53_King/csharp/Resources/InstructionsPrompt.txt similarity index 100% rename from 53_King/csharp/Resources/Instructions_Prompt.txt rename to 53_King/csharp/Resources/InstructionsPrompt.txt diff --git a/53_King/csharp/Resources/Instructions_Text.txt b/53_King/csharp/Resources/InstructionsText.txt similarity index 100% rename from 53_King/csharp/Resources/Instructions_Text.txt rename to 53_King/csharp/Resources/InstructionsText.txt diff --git a/53_King/csharp/Resources/Resource.cs b/53_King/csharp/Resources/Resource.cs index f8ddfd3f..6626a3dd 100644 --- a/53_King/csharp/Resources/Resource.cs +++ b/53_King/csharp/Resources/Resource.cs @@ -7,9 +7,17 @@ internal static class Resource { public static Stream Title => GetStream(); - public static string Instructions_Prompt => GetString(); - public static string Instructions_Text(int years) => string.Format(GetString(), years); + public static string InstructionsPrompt => GetString(); + public static string InstructionsText(int years) => string.Format(GetString(), years); + public static string SavedYearsPrompt => GetString(); + public static string SavedYearsError(int years) => string.Format(GetString(), years); + public static string SavedTreasuryPrompt => GetString(); + public static string SavedCountrymenPrompt => GetString(); + public static string SavedWorkersPrompt => GetString(); + public static string SavedLandPrompt => GetString(); + public static string SavedLandError => GetString(); + internal static class Formats { public static string Player => GetString(); diff --git a/53_King/csharp/Resources/SavedCountrymenPrompt.txt b/53_King/csharp/Resources/SavedCountrymenPrompt.txt new file mode 100644 index 00000000..120718d2 --- /dev/null +++ b/53_King/csharp/Resources/SavedCountrymenPrompt.txt @@ -0,0 +1 @@ +How many countrymen \ No newline at end of file diff --git a/53_King/csharp/Resources/SavedLandError.txt b/53_King/csharp/Resources/SavedLandError.txt new file mode 100644 index 00000000..9ac66dad --- /dev/null +++ b/53_King/csharp/Resources/SavedLandError.txt @@ -0,0 +1,2 @@ + Come on, you started with 1000 sq. miles of farm land + and 10,000 sq. miles of forest land diff --git a/53_King/csharp/Resources/SavedLandPrompt.txt b/53_King/csharp/Resources/SavedLandPrompt.txt new file mode 100644 index 00000000..e8afdf72 --- /dev/null +++ b/53_King/csharp/Resources/SavedLandPrompt.txt @@ -0,0 +1 @@ +How many square miles of land \ No newline at end of file diff --git a/53_King/csharp/Resources/SavedTreasuryPrompt.txt b/53_King/csharp/Resources/SavedTreasuryPrompt.txt new file mode 100644 index 00000000..f4a50f58 --- /dev/null +++ b/53_King/csharp/Resources/SavedTreasuryPrompt.txt @@ -0,0 +1 @@ +How much did you have in the treasury \ No newline at end of file diff --git a/53_King/csharp/Resources/SavedWorkersPrompt.txt b/53_King/csharp/Resources/SavedWorkersPrompt.txt new file mode 100644 index 00000000..b88a9c10 --- /dev/null +++ b/53_King/csharp/Resources/SavedWorkersPrompt.txt @@ -0,0 +1 @@ +How many workers \ No newline at end of file diff --git a/53_King/csharp/Resources/SavedYearsError.txt b/53_King/csharp/Resources/SavedYearsError.txt new file mode 100644 index 00000000..1c8c3c99 --- /dev/null +++ b/53_King/csharp/Resources/SavedYearsError.txt @@ -0,0 +1 @@ + Come on, your term in office is only {0} years. diff --git a/53_King/csharp/Resources/SavedYearsPrompt.txt b/53_King/csharp/Resources/SavedYearsPrompt.txt new file mode 100644 index 00000000..afbda229 --- /dev/null +++ b/53_King/csharp/Resources/SavedYearsPrompt.txt @@ -0,0 +1 @@ +How many years had you been in office when interrupted \ No newline at end of file From d4c5fb1df754931e27c3486b4527b1e01f31348c Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Tue, 1 Nov 2022 17:47:28 +1100 Subject: [PATCH 003/141] Add player input routines --- 53_King/csharp/Country.cs | 71 ++++++++++++++++++- 53_King/csharp/Game.cs | 2 +- 53_King/csharp/IOExtensions.cs | 28 ++++++-- 53_King/csharp/Program.cs | 1 + 53_King/csharp/Reign.cs | 14 +++- 53_King/csharp/Resources/GiveRallodsError.txt | 1 + .../csharp/Resources/GiveRallodsPrompt.txt | 1 + 53_King/csharp/Resources/PlantLandError1.txt | 1 + 53_King/csharp/Resources/PlantLandError2.txt | 1 + 53_King/csharp/Resources/PlantLandError3.txt | 1 + 53_King/csharp/Resources/PlantLandPrompt.txt | 1 + 53_King/csharp/Resources/PollutionError.txt | 1 + 53_King/csharp/Resources/PollutionPrompt.txt | 1 + 53_King/csharp/Resources/Resource.cs | 62 ++++++++++------ 53_King/csharp/Resources/SellLandError.txt | 2 + .../csharp/Resources/SellLandErrorReason.txt | 4 ++ 53_King/csharp/Resources/SellLandPrompt.txt | 1 + .../csharp/Resources/StatusSansWorkers.txt | 6 ++ .../csharp/Resources/StatusWithWorkers.txt | 6 ++ 53_King/csharp/ValidityTest.cs | 26 +++++++ 53_King/king.bas | 1 + 21 files changed, 201 insertions(+), 31 deletions(-) create mode 100644 53_King/csharp/Resources/GiveRallodsError.txt create mode 100644 53_King/csharp/Resources/GiveRallodsPrompt.txt create mode 100644 53_King/csharp/Resources/PlantLandError1.txt create mode 100644 53_King/csharp/Resources/PlantLandError2.txt create mode 100644 53_King/csharp/Resources/PlantLandError3.txt create mode 100644 53_King/csharp/Resources/PlantLandPrompt.txt create mode 100644 53_King/csharp/Resources/PollutionError.txt create mode 100644 53_King/csharp/Resources/PollutionPrompt.txt create mode 100644 53_King/csharp/Resources/SellLandError.txt create mode 100644 53_King/csharp/Resources/SellLandErrorReason.txt create mode 100644 53_King/csharp/Resources/SellLandPrompt.txt create mode 100644 53_King/csharp/Resources/StatusSansWorkers.txt create mode 100644 53_King/csharp/Resources/StatusWithWorkers.txt create mode 100644 53_King/csharp/ValidityTest.cs diff --git a/53_King/csharp/Country.cs b/53_King/csharp/Country.cs index 1e94e821..5b47c50a 100644 --- a/53_King/csharp/Country.cs +++ b/53_King/csharp/Country.cs @@ -2,6 +2,7 @@ namespace King; internal class Country { + private readonly IReadWrite _io; private readonly IRandom _random; private float _rallods; private float _countrymen; @@ -10,8 +11,9 @@ internal class Country private float _plantingCost; private float _landValue; - public Country(IRandom random) + public Country(IReadWrite io, IRandom random) : this( + io, random, (int)(60000 + random.NextFloat(1000) - random.NextFloat(1000)), (int)(500 + random.NextFloat(10) - random.NextFloat(10)), @@ -20,8 +22,9 @@ internal class Country { } - public Country(IRandom random, float rallods, float countrymen, float foreigners, float land) + public Country(IReadWrite io, IRandom random, float rallods, float countrymen, float foreigners, float land) { + _io = io; _random = random; _rallods = rallods; _countrymen = countrymen; @@ -31,4 +34,68 @@ internal class Country _plantingCost = random.Next(10, 15); _landValue = random.Next(95, 105); } + + public string Status => Resource.Status(_rallods, _countrymen, _foreigners, _land, _landValue, _plantingCost); + private float FarmLand => _land - 1000; + + public bool SellLand() + { + if (_io.TryReadValue( + SellLandPrompt, + out var landSold, + new ValidityTest(v => v <= FarmLand, () => SellLandError(FarmLand)))) + { + _land = (int)(_land - landSold); + _rallods = (int)(_rallods + landSold * _landValue); + return true; + } + + return false; + } + + public bool DistributeRallods() + { + if (_io.TryReadValue( + GiveRallodsPrompt, + out var rallodsGiven, + new ValidityTest(v => v <= _rallods, () => GiveRallodsError(_rallods)))) + { + _rallods = (int)(_rallods - rallodsGiven); + return true; + } + + return false; + } + + public bool PlantLand() + { + if (_rallods > 0 && + _io.TryReadValue( + PlantLandPrompt, + out var landPlanted, + new ValidityTest(v => v <= _countrymen * 2, PlantLandError1), + new ValidityTest(v => v <= FarmLand, PlantLandError2(FarmLand)), + new ValidityTest(v => v * _plantingCost <= _rallods, PlantLandError3(_rallods)))) + { + _rallods -= (int)(landPlanted * _plantingCost); + return true; + } + + return false; + } + + public bool ControlPollution() + { + if (_rallods > 0 && + _io.TryReadValue( + PollutionPrompt, + out var rallodsGiven, + new ValidityTest(v => v <= _rallods, () => PollutionError(_rallods)))) + { + _rallods = (int)(_rallods - rallodsGiven); + return true; + } + + return false; + } } diff --git a/53_King/csharp/Game.cs b/53_King/csharp/Game.cs index c8a44a8c..198ff14f 100644 --- a/53_King/csharp/Game.cs +++ b/53_King/csharp/Game.cs @@ -19,7 +19,7 @@ internal class Game if (SetUpReign() is Reign reign) { - _io.Write(reign); + reign.PlayYear(); } _io.WriteLine(); diff --git a/53_King/csharp/IOExtensions.cs b/53_King/csharp/IOExtensions.cs index 183621ca..c2707c0d 100644 --- a/53_King/csharp/IOExtensions.cs +++ b/53_King/csharp/IOExtensions.cs @@ -13,7 +13,7 @@ internal static class IOExtensions io.TryReadValue(SavedWorkersPrompt, out var workers) && io.TryReadValue(SavedLandPrompt, v => v is > 1000 and <= 2000, SavedLandError, out var land)) { - reign = new Reign(io, new Country(random, rallods, countrymen, workers, land), years + 1); + reign = new Reign(io, new Country(io, random, rallods, countrymen, workers, land), years + 1); return true; } @@ -21,15 +21,33 @@ internal static class IOExtensions return false; } - private static bool TryReadValue(this IReadWrite io, string prompt, out float value) + internal static bool TryReadValue(this IReadWrite io, string prompt, out float value, params ValidityTest[] tests) + { + while (true) + { + var response = value = io.ReadNumber(prompt); + if (response < 0) { return false; } + if (tests.All(test => test.IsValid(response, io))) { return true; } + } + } + + internal static bool TryReadValue(this IReadWrite io, string prompt, out float value) => io.TryReadValue(prompt, _ => true, "", out value); - private static bool TryReadValue( + internal static bool TryReadValue( this IReadWrite io, string prompt, Predicate isValid, string error, out float value) + => io.TryReadValue(prompt, isValid, () => error, out value); + + internal static bool TryReadValue( + this IReadWrite io, + string prompt, + Predicate isValid, + Func getError, + out float value) { while (true) { @@ -37,7 +55,7 @@ internal static class IOExtensions if (value < 0) { return false; } if (isValid(value)) { return true; } - io.Write(error); + io.Write(getError()); } } -} \ No newline at end of file +} diff --git a/53_King/csharp/Program.cs b/53_King/csharp/Program.cs index 6eb4c50e..5aae4ccb 100644 --- a/53_King/csharp/Program.cs +++ b/53_King/csharp/Program.cs @@ -1,6 +1,7 @@ global using Games.Common.IO; global using Games.Common.Randomness; global using King.Resources; +global using static King.Resources.Resource; using King; new Game(new ConsoleIO(), new RandomNumberGenerator()).Play(); diff --git a/53_King/csharp/Reign.cs b/53_King/csharp/Reign.cs index 4e6c45c5..58e89b66 100644 --- a/53_King/csharp/Reign.cs +++ b/53_King/csharp/Reign.cs @@ -9,9 +9,8 @@ internal class Reign private readonly float _year; public Reign(IReadWrite io, IRandom random) - : this(io, new Country(random), 0) + : this(io, new Country(io, random), 0) { - } public Reign(IReadWrite io, Country country, float year) @@ -20,4 +19,15 @@ internal class Reign _country = country; _year = year; } + + public void PlayYear() + { + _io.Write(_country.Status); + + var playerSoldLand = _country.SellLand(); + var playerDistributedRallods = _country.DistributeRallods(); + var playerPlantedLand = _country.PlantLand(); + var playerControlledPollution = _country.ControlPollution(); + + } } diff --git a/53_King/csharp/Resources/GiveRallodsError.txt b/53_King/csharp/Resources/GiveRallodsError.txt new file mode 100644 index 00000000..df0d7cc0 --- /dev/null +++ b/53_King/csharp/Resources/GiveRallodsError.txt @@ -0,0 +1 @@ + Think again. You've only got {0} rallods in the treasury. diff --git a/53_King/csharp/Resources/GiveRallodsPrompt.txt b/53_King/csharp/Resources/GiveRallodsPrompt.txt new file mode 100644 index 00000000..e8bd345b --- /dev/null +++ b/53_King/csharp/Resources/GiveRallodsPrompt.txt @@ -0,0 +1 @@ +How many rallods will you distribute among your countrymen \ No newline at end of file diff --git a/53_King/csharp/Resources/PlantLandError1.txt b/53_King/csharp/Resources/PlantLandError1.txt new file mode 100644 index 00000000..ef959f9f --- /dev/null +++ b/53_King/csharp/Resources/PlantLandError1.txt @@ -0,0 +1 @@ + Sorry, but each countryman can only plant 2 sq. miles. \ No newline at end of file diff --git a/53_King/csharp/Resources/PlantLandError2.txt b/53_King/csharp/Resources/PlantLandError2.txt new file mode 100644 index 00000000..e5844aa1 --- /dev/null +++ b/53_King/csharp/Resources/PlantLandError2.txt @@ -0,0 +1 @@ + Sorry, but you've only {0} sq. miles of farm land. \ No newline at end of file diff --git a/53_King/csharp/Resources/PlantLandError3.txt b/53_King/csharp/Resources/PlantLandError3.txt new file mode 100644 index 00000000..38264442 --- /dev/null +++ b/53_King/csharp/Resources/PlantLandError3.txt @@ -0,0 +1 @@ + Think again, You've only {0} rallods left in the treasury. diff --git a/53_King/csharp/Resources/PlantLandPrompt.txt b/53_King/csharp/Resources/PlantLandPrompt.txt new file mode 100644 index 00000000..f2fe448e --- /dev/null +++ b/53_King/csharp/Resources/PlantLandPrompt.txt @@ -0,0 +1 @@ +How many square miles do you wish to plant \ No newline at end of file diff --git a/53_King/csharp/Resources/PollutionError.txt b/53_King/csharp/Resources/PollutionError.txt new file mode 100644 index 00000000..f99644e3 --- /dev/null +++ b/53_King/csharp/Resources/PollutionError.txt @@ -0,0 +1 @@ + Think again. You only have {0} rallods remaining. diff --git a/53_King/csharp/Resources/PollutionPrompt.txt b/53_King/csharp/Resources/PollutionPrompt.txt new file mode 100644 index 00000000..252fb333 --- /dev/null +++ b/53_King/csharp/Resources/PollutionPrompt.txt @@ -0,0 +1 @@ +How many rallods do you wish to spend on pollution control \ No newline at end of file diff --git a/53_King/csharp/Resources/Resource.cs b/53_King/csharp/Resources/Resource.cs index 6626a3dd..37ff09eb 100644 --- a/53_King/csharp/Resources/Resource.cs +++ b/53_King/csharp/Resources/Resource.cs @@ -5,11 +5,52 @@ namespace King.Resources; internal static class Resource { + private static bool _sellLandErrorShown; + public static Stream Title => GetStream(); public static string InstructionsPrompt => GetString(); public static string InstructionsText(int years) => string.Format(GetString(), years); + public static string Status( + float rallods, + float countrymen, + float workers, + float land, + float landValue, + float plantingCost) + => string.Format( + workers == 0 ? StatusWithWorkers : StatusSansWorkers, + rallods, + (int)countrymen, + (int)workers, + (int)land, + landValue, + plantingCost); + + private static string StatusWithWorkers => GetString(); + private static string StatusSansWorkers => GetString(); + + public static string SellLandPrompt => GetString(); + public static string SellLandError(float farmLand) + { + var error = string.Format(GetString(), farmLand, _sellLandErrorShown ? "" : SellLandErrorReason); + _sellLandErrorShown = true; + return error; + } + private static string SellLandErrorReason => GetString(); + + public static string GiveRallodsPrompt => GetString(); + public static string GiveRallodsError(float rallods) => string.Format(GetString(), rallods); + + public static string PlantLandPrompt => GetString(); + public static string PlantLandError1 => GetString(); + public static string PlantLandError2(float farmLand) => string.Format(GetString(), farmLand); + public static string PlantLandError3(float rallods) => string.Format(GetString(), rallods); + + public static string PollutionPrompt => GetString(); + public static string PollutionError(float rallods) => string.Format(GetString(), rallods); + public static string SavedYearsPrompt => GetString(); public static string SavedYearsError(int years) => string.Format(GetString(), years); public static string SavedTreasuryPrompt => GetString(); @@ -17,27 +58,6 @@ internal static class Resource public static string SavedWorkersPrompt => GetString(); public static string SavedLandPrompt => GetString(); public static string SavedLandError => GetString(); - - internal static class Formats - { - public static string Player => GetString(); - public static string YouLose => GetString(); - } - - internal static class Prompts - { - public static string WantInstructions => GetString(); - public static string HowManyPlayers => GetString(); - public static string HowManyRows => GetString(); - public static string HowManyColumns => GetString(); - public static string TooManyColumns => GetString(); - } - - internal static class Strings - { - public static string TooManyColumns => GetString(); - public static string TooManyRows => GetString(); - } private static string GetString([CallerMemberName] string? name = null) { diff --git a/53_King/csharp/Resources/SellLandError.txt b/53_King/csharp/Resources/SellLandError.txt new file mode 100644 index 00000000..83e0266c --- /dev/null +++ b/53_King/csharp/Resources/SellLandError.txt @@ -0,0 +1,2 @@ +*** Think again. You only have {0} square miles of farm land. +{1} \ No newline at end of file diff --git a/53_King/csharp/Resources/SellLandErrorReason.txt b/53_King/csharp/Resources/SellLandErrorReason.txt new file mode 100644 index 00000000..836352bb --- /dev/null +++ b/53_King/csharp/Resources/SellLandErrorReason.txt @@ -0,0 +1,4 @@ + +(Foreign industry will only buy farm land because +forest land is uneconomical to strip mine due to trees, +thicker top soil, etc.) diff --git a/53_King/csharp/Resources/SellLandPrompt.txt b/53_King/csharp/Resources/SellLandPrompt.txt new file mode 100644 index 00000000..8d01c2c2 --- /dev/null +++ b/53_King/csharp/Resources/SellLandPrompt.txt @@ -0,0 +1 @@ +How many square miles do you wish to sell to industry \ No newline at end of file diff --git a/53_King/csharp/Resources/StatusSansWorkers.txt b/53_King/csharp/Resources/StatusSansWorkers.txt new file mode 100644 index 00000000..7efb84e7 --- /dev/null +++ b/53_King/csharp/Resources/StatusSansWorkers.txt @@ -0,0 +1,6 @@ + +You now have {0} rallods in the treasury. + {1} countrymen, and {3} sq. miles of land. +This year industry will buy land for {4} rallods per square mile. +Land currently costs {5} rallods per square mile to plant. + diff --git a/53_King/csharp/Resources/StatusWithWorkers.txt b/53_King/csharp/Resources/StatusWithWorkers.txt new file mode 100644 index 00000000..e7bc5c4a --- /dev/null +++ b/53_King/csharp/Resources/StatusWithWorkers.txt @@ -0,0 +1,6 @@ + +You now have {0} rallods in the treasury. + {1} countrymen, {2} foreign workers and {3} sq. miles of land. +This year industry will buy land for {4} rallods per square mile. +Land currently costs {5} rallods per square mile to plant. + diff --git a/53_King/csharp/ValidityTest.cs b/53_King/csharp/ValidityTest.cs new file mode 100644 index 00000000..f1aeb5ec --- /dev/null +++ b/53_King/csharp/ValidityTest.cs @@ -0,0 +1,26 @@ +namespace King; + +internal class ValidityTest +{ + private readonly Predicate _isValid; + private readonly Func _getError; + + public ValidityTest(Predicate isValid, string error) + : this(isValid, () => error) + { + } + + public ValidityTest(Predicate isValid, Func getError) + { + _isValid = isValid; + _getError = getError; + } + + public bool IsValid(float value, IReadWrite io) + { + if (_isValid(value)) { return true; } + + io.Write(_getError()); + return false; + } +} \ No newline at end of file diff --git a/53_King/king.bas b/53_King/king.bas index 7352fa58..5e9ae163 100644 --- a/53_King/king.bas +++ b/53_King/king.bas @@ -99,6 +99,7 @@ 1000 GOTO 600 1002 PRINT 1003 PRINT +1005 PRINT A 1010 A=INT(A-K) 1020 A4=A 1100 IF INT(I/100-B)>=0 THEN 1120 From ab7f2c11827160348eab05162e5191734a1d10a8 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Wed, 2 Nov 2022 08:00:27 +1100 Subject: [PATCH 004/141] Add game loop and end --- 53_King/csharp/Game.cs | 5 +++-- 53_King/csharp/IOExtensions.cs | 2 +- 53_King/csharp/Reign.cs | 18 ++++++++++++++---- 53_King/csharp/Resources/Goodbye.txt | 5 +++++ 53_King/csharp/Resources/Resource.cs | 2 ++ 5 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 53_King/csharp/Resources/Goodbye.txt diff --git a/53_King/csharp/Game.cs b/53_King/csharp/Game.cs index 198ff14f..92d8063f 100644 --- a/53_King/csharp/Game.cs +++ b/53_King/csharp/Game.cs @@ -17,9 +17,10 @@ internal class Game { _io.Write(Resource.Title); - if (SetUpReign() is Reign reign) + var reign = SetUpReign(); + if (reign != null) { - reign.PlayYear(); + while (reign.PlayYear()); } _io.WriteLine(); diff --git a/53_King/csharp/IOExtensions.cs b/53_King/csharp/IOExtensions.cs index c2707c0d..e59bf4c0 100644 --- a/53_King/csharp/IOExtensions.cs +++ b/53_King/csharp/IOExtensions.cs @@ -26,7 +26,7 @@ internal static class IOExtensions while (true) { var response = value = io.ReadNumber(prompt); - if (response < 0) { return false; } + if (response == 0) { return false; } if (tests.All(test => test.IsValid(response, io))) { return true; } } } diff --git a/53_King/csharp/Reign.cs b/53_King/csharp/Reign.cs index 58e89b66..2bdc9b89 100644 --- a/53_King/csharp/Reign.cs +++ b/53_King/csharp/Reign.cs @@ -6,10 +6,10 @@ internal class Reign private readonly IReadWrite _io; private readonly Country _country; - private readonly float _year; + private float _year; public Reign(IReadWrite io, IRandom random) - : this(io, new Country(io, random), 0) + : this(io, new Country(io, random), 1) { } @@ -20,7 +20,7 @@ internal class Reign _year = year; } - public void PlayYear() + public bool PlayYear() { _io.Write(_country.Status); @@ -28,6 +28,16 @@ internal class Reign var playerDistributedRallods = _country.DistributeRallods(); var playerPlantedLand = _country.PlantLand(); var playerControlledPollution = _country.ControlPollution(); - + + if (playerSoldLand || playerDistributedRallods || playerPlantedLand || playerControlledPollution) + { + _year++; + return true; + } + else + { + _io.Write(Goodbye); + return false; + } } } diff --git a/53_King/csharp/Resources/Goodbye.txt b/53_King/csharp/Resources/Goodbye.txt new file mode 100644 index 00000000..325890a5 --- /dev/null +++ b/53_King/csharp/Resources/Goodbye.txt @@ -0,0 +1,5 @@ + +Goodbye. +(If you wish to continue this game at a later date, answer +'again' when asked if you want instructions at the start +of the game). diff --git a/53_King/csharp/Resources/Resource.cs b/53_King/csharp/Resources/Resource.cs index 37ff09eb..cf555583 100644 --- a/53_King/csharp/Resources/Resource.cs +++ b/53_King/csharp/Resources/Resource.cs @@ -59,6 +59,8 @@ internal static class Resource public static string SavedLandPrompt => GetString(); public static string SavedLandError => GetString(); + public static string Goodbye => GetString(); + private static string GetString([CallerMemberName] string? name = null) { using var stream = GetStream(name); From 9d8a4fbc93c1cdfa4fda0b9afbdfc1ad6239952c Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Tue, 29 Nov 2022 22:40:34 +1100 Subject: [PATCH 005/141] Add evaluation of deaths --- 53_King/csharp/Country.cs | 71 ++++++++------ 53_King/csharp/IOExtensions.cs | 2 +- 53_King/csharp/Reign.cs | 25 ++--- 53_King/csharp/Resources/DeathsPollution.txt | 1 + 53_King/csharp/Resources/DeathsStarvation.txt | 1 + 53_King/csharp/Resources/EndAlso.txt | 3 + .../csharp/Resources/EndCongratulations.txt | 10 ++ 53_King/csharp/Resources/EndConsequences.txt | 3 + .../csharp/Resources/EndForeignWorkers.txt | 7 ++ 53_King/csharp/Resources/EndManyDead.txt | 5 + 53_King/csharp/Resources/EndMoneyLeftOver.txt | 7 ++ 53_King/csharp/Resources/EndOneThirdDead.txt | 7 ++ 53_King/csharp/Resources/FuneralExpenses.txt | 1 + 53_King/csharp/Resources/Goodbye.txt | 1 - .../csharp/Resources/InsufficientReserves.txt | 0 53_King/csharp/Resources/PollutionEffect.txt | 5 + 53_King/csharp/Resources/Resource.cs | 24 +++++ 53_King/csharp/Year.cs | 93 +++++++++++++++++++ 53_King/king.bas | 1 - 19 files changed, 226 insertions(+), 41 deletions(-) create mode 100644 53_King/csharp/Resources/DeathsPollution.txt create mode 100644 53_King/csharp/Resources/DeathsStarvation.txt create mode 100644 53_King/csharp/Resources/EndAlso.txt create mode 100644 53_King/csharp/Resources/EndCongratulations.txt create mode 100644 53_King/csharp/Resources/EndConsequences.txt create mode 100644 53_King/csharp/Resources/EndForeignWorkers.txt create mode 100644 53_King/csharp/Resources/EndManyDead.txt create mode 100644 53_King/csharp/Resources/EndMoneyLeftOver.txt create mode 100644 53_King/csharp/Resources/EndOneThirdDead.txt create mode 100644 53_King/csharp/Resources/FuneralExpenses.txt create mode 100644 53_King/csharp/Resources/InsufficientReserves.txt create mode 100644 53_King/csharp/Resources/PollutionEffect.txt create mode 100644 53_King/csharp/Year.cs diff --git a/53_King/csharp/Country.cs b/53_King/csharp/Country.cs index 5b47c50a..b1b473e7 100644 --- a/53_King/csharp/Country.cs +++ b/53_King/csharp/Country.cs @@ -2,14 +2,15 @@ namespace King; internal class Country { + private const int InitialLand = 1000; + private readonly IReadWrite _io; private readonly IRandom _random; private float _rallods; private float _countrymen; private float _foreigners; - private float _land; - private float _plantingCost; - private float _landValue; + private float _arableLand; + private float _industryLand; public Country(IReadWrite io, IRandom random) : this( @@ -18,7 +19,7 @@ internal class Country (int)(60000 + random.NextFloat(1000) - random.NextFloat(1000)), (int)(500 + random.NextFloat(10) - random.NextFloat(10)), 0, - 2000) + InitialLand) { } @@ -29,35 +30,38 @@ internal class Country _rallods = rallods; _countrymen = countrymen; _foreigners = foreigners; - _land = land; - - _plantingCost = random.Next(10, 15); - _landValue = random.Next(95, 105); + _arableLand = land; } - public string Status => Resource.Status(_rallods, _countrymen, _foreigners, _land, _landValue, _plantingCost); - private float FarmLand => _land - 1000; + public string GetStatus(int landValue, int plantingCost) + => Resource.Status(_rallods, _countrymen, _foreigners, _arableLand, landValue, plantingCost); + + public float Countrymen => _countrymen; + private float FarmLand => _arableLand; + public bool HasRallods => _rallods > 0; + public float Rallods => _rallods; + public float IndustryLand => InitialLand - _arableLand; - public bool SellLand() + public bool SellLand(int landValue, out float landSold) { if (_io.TryReadValue( SellLandPrompt, - out var landSold, + out landSold, new ValidityTest(v => v <= FarmLand, () => SellLandError(FarmLand)))) { - _land = (int)(_land - landSold); - _rallods = (int)(_rallods + landSold * _landValue); + _arableLand = (int)(_arableLand - landSold); + _rallods = (int)(_rallods + landSold * landValue); return true; } return false; } - public bool DistributeRallods() + public bool DistributeRallods(out float rallodsGiven) { if (_io.TryReadValue( GiveRallodsPrompt, - out var rallodsGiven, + out rallodsGiven, new ValidityTest(v => v <= _rallods, () => GiveRallodsError(_rallods)))) { _rallods = (int)(_rallods - rallodsGiven); @@ -67,35 +71,48 @@ internal class Country return false; } - public bool PlantLand() + public bool PlantLand(int plantingCost, out float landPlanted) { - if (_rallods > 0 && - _io.TryReadValue( + if (_io.TryReadValue( PlantLandPrompt, - out var landPlanted, + out landPlanted, new ValidityTest(v => v <= _countrymen * 2, PlantLandError1), new ValidityTest(v => v <= FarmLand, PlantLandError2(FarmLand)), - new ValidityTest(v => v * _plantingCost <= _rallods, PlantLandError3(_rallods)))) + new ValidityTest(v => v * plantingCost <= _rallods, PlantLandError3(_rallods)))) { - _rallods -= (int)(landPlanted * _plantingCost); + _rallods -= (int)(landPlanted * plantingCost); return true; } return false; } - public bool ControlPollution() + public bool ControlPollution(out float rallodsSpent) { - if (_rallods > 0 && - _io.TryReadValue( + if (_io.TryReadValue( PollutionPrompt, - out var rallodsGiven, + out rallodsSpent, new ValidityTest(v => v <= _rallods, () => PollutionError(_rallods)))) { - _rallods = (int)(_rallods - rallodsGiven); + _rallods = (int)(_rallods - rallodsSpent); return true; } return false; } + + public bool TrySpend(float amount, float landValue) + { + if (_rallods >= amount) + { + _rallods -= amount; + return true; + } + + _arableLand = (int)(_arableLand - (int)(amount - _rallods) / landValue); + _rallods = 0; + return false; + } + + public void RemoveTheDead(int deaths) => _countrymen = (int)(_countrymen - deaths); } diff --git a/53_King/csharp/IOExtensions.cs b/53_King/csharp/IOExtensions.cs index e59bf4c0..dbb3079a 100644 --- a/53_King/csharp/IOExtensions.cs +++ b/53_King/csharp/IOExtensions.cs @@ -13,7 +13,7 @@ internal static class IOExtensions io.TryReadValue(SavedWorkersPrompt, out var workers) && io.TryReadValue(SavedLandPrompt, v => v is > 1000 and <= 2000, SavedLandError, out var land)) { - reign = new Reign(io, new Country(io, random, rallods, countrymen, workers, land), years + 1); + reign = new Reign(io, random, new Country(io, random, rallods, countrymen, workers, land), years + 1); return true; } diff --git a/53_King/csharp/Reign.cs b/53_King/csharp/Reign.cs index 2bdc9b89..727f71d3 100644 --- a/53_King/csharp/Reign.cs +++ b/53_King/csharp/Reign.cs @@ -5,37 +5,40 @@ internal class Reign public const int MaxTerm = 8; private readonly IReadWrite _io; + private readonly IRandom _random; private readonly Country _country; - private float _year; + private float _yearNumber; public Reign(IReadWrite io, IRandom random) - : this(io, new Country(io, random), 1) + : this(io, random, new Country(io, random), 1) { } - public Reign(IReadWrite io, Country country, float year) + public Reign(IReadWrite io, IRandom random, Country country, float year) { _io = io; + _random = random; _country = country; - _year = year; + _yearNumber = year; } public bool PlayYear() { - _io.Write(_country.Status); + var year = new Year(_country, _random); - var playerSoldLand = _country.SellLand(); - var playerDistributedRallods = _country.DistributeRallods(); - var playerPlantedLand = _country.PlantLand(); - var playerControlledPollution = _country.ControlPollution(); + _io.Write(year.Status); - if (playerSoldLand || playerDistributedRallods || playerPlantedLand || playerControlledPollution) + if (year.GetPlayerActions()) { - _year++; + _io.WriteLine(); + _io.WriteLine(); + year.EvaluateResults(_io, _random); + _yearNumber++; return true; } else { + _io.WriteLine(); _io.Write(Goodbye); return false; } diff --git a/53_King/csharp/Resources/DeathsPollution.txt b/53_King/csharp/Resources/DeathsPollution.txt new file mode 100644 index 00000000..bf28d9a7 --- /dev/null +++ b/53_King/csharp/Resources/DeathsPollution.txt @@ -0,0 +1 @@ + {0} countrymen died of carbon-monoxide and dust inhalation \ No newline at end of file diff --git a/53_King/csharp/Resources/DeathsStarvation.txt b/53_King/csharp/Resources/DeathsStarvation.txt new file mode 100644 index 00000000..af275cf4 --- /dev/null +++ b/53_King/csharp/Resources/DeathsStarvation.txt @@ -0,0 +1 @@ + {0} countrymen died of starvation \ No newline at end of file diff --git a/53_King/csharp/Resources/EndAlso.txt b/53_King/csharp/Resources/EndAlso.txt new file mode 100644 index 00000000..084b9a82 --- /dev/null +++ b/53_King/csharp/Resources/EndAlso.txt @@ -0,0 +1,3 @@ +also had your left eye gouged out! +;have also gained a very bad reputation. +;have also been declared national fink. diff --git a/53_King/csharp/Resources/EndCongratulations.txt b/53_King/csharp/Resources/EndCongratulations.txt new file mode 100644 index 00000000..ef1ff203 --- /dev/null +++ b/53_King/csharp/Resources/EndCongratulations.txt @@ -0,0 +1,10 @@ + + +Congratulations!!!!!!!!!!!!!!!!!! +You have successfully completed your {0} year term +of office. You were, of course, extremely lucky, but +nevertheless, it's quite an achievement. Goodbye and good +luck - you'll probably need it if you're the type that +plays this game. + + diff --git a/53_King/csharp/Resources/EndConsequences.txt b/53_King/csharp/Resources/EndConsequences.txt new file mode 100644 index 00000000..61ebfd25 --- /dev/null +++ b/53_King/csharp/Resources/EndConsequences.txt @@ -0,0 +1,3 @@ +You have been thrown out of office and are now +residing in prison.; +You have been assassinated. diff --git a/53_King/csharp/Resources/EndForeignWorkers.txt b/53_King/csharp/Resources/EndForeignWorkers.txt new file mode 100644 index 00000000..76c04c3d --- /dev/null +++ b/53_King/csharp/Resources/EndForeignWorkers.txt @@ -0,0 +1,7 @@ + + +The number of foreign workers has exceeded the number +of countrymen. As a minority, they have revolted and +taken over the country. +{0} + diff --git a/53_King/csharp/Resources/EndManyDead.txt b/53_King/csharp/Resources/EndManyDead.txt new file mode 100644 index 00000000..70cd95f0 --- /dev/null +++ b/53_King/csharp/Resources/EndManyDead.txt @@ -0,0 +1,5 @@ +{0} countrymen died in one year!!!!! +due to this extreme mismanagement, you have not only +been impeached and thrown out of office, but you +{1} + diff --git a/53_King/csharp/Resources/EndMoneyLeftOver.txt b/53_King/csharp/Resources/EndMoneyLeftOver.txt new file mode 100644 index 00000000..e194a9fd --- /dev/null +++ b/53_King/csharp/Resources/EndMoneyLeftOver.txt @@ -0,0 +1,7 @@ + +Money was left over in the treasury which you did +not spend. As a result, some of your countrymen died +of starvation. The public is enraged and you have +been forced to resign. + + diff --git a/53_King/csharp/Resources/EndOneThirdDead.txt b/53_King/csharp/Resources/EndOneThirdDead.txt new file mode 100644 index 00000000..e1761dfe --- /dev/null +++ b/53_King/csharp/Resources/EndOneThirdDead.txt @@ -0,0 +1,7 @@ + + +Over one third of the population has died since you +were elected to office. The people (remaining) +hate your guts. +{0} + diff --git a/53_King/csharp/Resources/FuneralExpenses.txt b/53_King/csharp/Resources/FuneralExpenses.txt new file mode 100644 index 00000000..3aff58eb --- /dev/null +++ b/53_King/csharp/Resources/FuneralExpenses.txt @@ -0,0 +1 @@ + You were forced to spend {0} rallods on funeral expenses \ No newline at end of file diff --git a/53_King/csharp/Resources/Goodbye.txt b/53_King/csharp/Resources/Goodbye.txt index 325890a5..67543c04 100644 --- a/53_King/csharp/Resources/Goodbye.txt +++ b/53_King/csharp/Resources/Goodbye.txt @@ -1,4 +1,3 @@ - Goodbye. (If you wish to continue this game at a later date, answer 'again' when asked if you want instructions at the start diff --git a/53_King/csharp/Resources/InsufficientReserves.txt b/53_King/csharp/Resources/InsufficientReserves.txt new file mode 100644 index 00000000..e69de29b diff --git a/53_King/csharp/Resources/PollutionEffect.txt b/53_King/csharp/Resources/PollutionEffect.txt new file mode 100644 index 00000000..b3535c3d --- /dev/null +++ b/53_King/csharp/Resources/PollutionEffect.txt @@ -0,0 +1,5 @@ +fish population has dwindled due to water pollution. +;air pollution is killing game bird population. +;mineral baths are being ruined by water pollution. +;unpleasant smog is discouraging sun bathers. +;hotels are looking shabby due to smog grit. diff --git a/53_King/csharp/Resources/Resource.cs b/53_King/csharp/Resources/Resource.cs index cf555583..496bc250 100644 --- a/53_King/csharp/Resources/Resource.cs +++ b/53_King/csharp/Resources/Resource.cs @@ -51,6 +51,28 @@ internal static class Resource public static string PollutionPrompt => GetString(); public static string PollutionError(float rallods) => string.Format(GetString(), rallods); + public static string DeathsStarvation(float deaths) => string.Format(GetString(), (int)deaths); + public static string DeathsPollution(int deaths) => string.Format(GetString(), deaths); + public static string FuneralExpenses(int expenses) => string.Format(GetString(), expenses); + public static string InsufficientReserves => GetString(); + + private static string PollutionEffect(IRandom random) => GetStrings()[random.Next(5)]; + + private static string EndAlso(IRandom random) + => random.Next(10) switch + { + <= 3 => GetStrings()[0], + <= 6 => GetStrings()[1], + _ => GetStrings()[2] + }; + + public static string EndCongratulations(int termLength) => string.Format(GetString(), termLength); + private static string EndConsequences(IRandom random) => GetStrings()[random.Next(2)]; + public static string EndForeignWorkers(IRandom random) => string.Format(GetString(), EndConsequences(random)); + public static string EndManyDead(int deaths, IRandom random) => string.Format(GetString(), deaths, EndAlso(random)); + public static string EndMoneyLeftOver(int termLength) => string.Format(GetString(), termLength); + public static string EndOneThirdDead(IRandom random) => string.Format(GetString(), EndConsequences(random)); + public static string SavedYearsPrompt => GetString(); public static string SavedYearsError(int years) => string.Format(GetString(), years); public static string SavedTreasuryPrompt => GetString(); @@ -61,6 +83,8 @@ internal static class Resource public static string Goodbye => GetString(); + private static string[] GetStrings([CallerMemberName] string? name = null) => GetString(name).Split(';'); + private static string GetString([CallerMemberName] string? name = null) { using var stream = GetStream(name); diff --git a/53_King/csharp/Year.cs b/53_King/csharp/Year.cs new file mode 100644 index 00000000..18f208e4 --- /dev/null +++ b/53_King/csharp/Year.cs @@ -0,0 +1,93 @@ +using System.Text; + +namespace King; + +internal class Year +{ + private readonly Country _country; + private readonly IRandom _random; + private readonly int _plantingCost; + private readonly int _landValue; + + private float _landSold; + private float _rallodsDistributed; + private float _landPlanted; + private float _pollutionControlCost; + + public Year(Country country, IRandom random) + { + _country = country; + _random = random; + + _plantingCost = random.Next(10, 15); + _landValue = random.Next(95, 105); + } + + public string Status => _country.GetStatus(_landValue, _plantingCost); + + public bool GetPlayerActions() + { + var playerSoldLand = _country.SellLand(_landValue, out _landSold); + var playerDistributedRallods = _country.DistributeRallods(out _rallodsDistributed); + var playerPlantedLand = _country.HasRallods && _country.PlantLand(_plantingCost, out _landPlanted); + var playerControlledPollution = _country.HasRallods && _country.ControlPollution(out _pollutionControlCost); + + return playerSoldLand || playerDistributedRallods || playerPlantedLand || playerControlledPollution; + } + + public Result EvaluateResults(IReadWrite io) + { + var unspentRallods = _country.Rallods; + var statusUpdate = new StringBuilder(); + + var result = EvaluateDeaths(statusUpdate, out var deaths); + + io.Write(statusUpdate); + + return Result.Continue; + } + + public Result? EvaluateDeaths(StringBuilder statusUpdate, out int deaths) + { + deaths = default; + + var supportedCountrymen = _rallodsDistributed / 100; + var starvationDeaths = _country.Countrymen - supportedCountrymen; + if (starvationDeaths > 0) + { + if (supportedCountrymen < 50) { return Result.GameOver(EndOneThirdDead(_random)); } + statusUpdate.AppendLine(DeathsStarvation(starvationDeaths)); + } + + var pollutionControl = _pollutionControlCost >= 25 ? _pollutionControlCost / 25 : 1; + var pollutionDeaths = (int)(_random.Next((int)_country.IndustryLand) / pollutionControl); + if (pollutionDeaths > 0) + { + statusUpdate.AppendLine(DeathsPollution(pollutionDeaths)); + } + + deaths = (int)(starvationDeaths + pollutionDeaths); + if (deaths > 0) + { + var funeralCosts = deaths * 9; + statusUpdate.AppendLine(FuneralExpenses(funeralCosts)); + + if (!_country.TrySpend(funeralCosts, _landValue)) + { + statusUpdate.AppendLine(InsufficientReserves); + } + + _country.RemoveTheDead(deaths); + } + + return null; + } + + + internal record struct Result (bool IsGameOver, string Message) + { + internal static Result GameOver(string message) => new(true, message); + internal static Result Continue => new(false, ""); + } +} + diff --git a/53_King/king.bas b/53_King/king.bas index 5e9ae163..7352fa58 100644 --- a/53_King/king.bas +++ b/53_King/king.bas @@ -99,7 +99,6 @@ 1000 GOTO 600 1002 PRINT 1003 PRINT -1005 PRINT A 1010 A=INT(A-K) 1020 A4=A 1100 IF INT(I/100-B)>=0 THEN 1120 From 051f3eb5d5d2595cb719fe7eea0c072166ac4fae Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Thu, 19 Jan 2023 07:36:00 +1100 Subject: [PATCH 006/141] Add evaluation of migration and agriculture --- 53_King/README.md | 16 ++++- 53_King/csharp/Country.cs | 7 ++ 53_King/csharp/Reign.cs | 4 +- 53_King/csharp/Resources/Emigration.txt | 1 + 53_King/csharp/Resources/Harvest.txt | 2 + 53_King/csharp/Resources/HarvestReason.txt | 1 + 53_King/csharp/Resources/Immigration.txt | 1 + 53_King/csharp/Resources/LandPlanted.txt | 1 + 53_King/csharp/Resources/Resource.cs | 12 ++++ 53_King/csharp/Resources/WorkerMigration.txt | 1 + 53_King/csharp/Year.cs | 73 ++++++++++++++------ 11 files changed, 95 insertions(+), 24 deletions(-) create mode 100644 53_King/csharp/Resources/Emigration.txt create mode 100644 53_King/csharp/Resources/Harvest.txt create mode 100644 53_King/csharp/Resources/HarvestReason.txt create mode 100644 53_King/csharp/Resources/Immigration.txt create mode 100644 53_King/csharp/Resources/LandPlanted.txt create mode 100644 53_King/csharp/Resources/WorkerMigration.txt diff --git a/53_King/README.md b/53_King/README.md index a363a2be..14a66fe9 100644 --- a/53_King/README.md +++ b/53_King/README.md @@ -1,10 +1,10 @@ ## King -This is one of the most comprehensive, difficult, and interesting games. (If you’ve never played one of these games, start with HAMMURABI.) +This is one of the most comprehensive, difficult, and interesting games. (If you've never played one of these games, start with HAMMURABI.) In this game, you are Premier of Setats Detinu, a small communist island 30 by 70 miles long. Your job is to decide upon the budget of your country and distribute money to your country from the communal treasury. -The money system is Rollods; each person needs 100 Rallods per year to survive. Your country’s income comes from farm produce and tourists visiting your magnificent forests, hunting, fishing, etc. Part of your land is farm land but it also has an excellent mineral content and may be sold to foreign industry for strip mining. Industry import and support their own workers. Crops cost between 10 and 15 Rallods per square mile to plant, cultivate, and harvest. Your goal is to complete an eight-year term of office without major mishap. A word of warning: it isn’t easy! +The money system is Rollods; each person needs 100 Rallods per year to survive. Your country's income comes from farm produce and tourists visiting your magnificent forests, hunting, fishing, etc. Part of your land is farm land but it also has an excellent mineral content and may be sold to foreign industry for strip mining. Industry import and support their own workers. Crops cost between 10 and 15 Rallods per square mile to plant, cultivate, and harvest. Your goal is to complete an eight-year term of office without major mishap. A word of warning: it isn't easy! The author of this program is James A. Storer who wrote it while a student at Lexington High School. @@ -66,3 +66,15 @@ On basic line 1997 it is: but it should be: 1997 PRINT " AND 1,000 SQ. MILES OF FOREST LAND." + +### Bug 4 + +On basic line 1310 we see this: + + 1310 IF C=0 THEN 1324 + 1320 PRINT "OF ";INT(J);"SQ. MILES PLANTED,"; + 1324 ... + +but it should probably be: + + 1310 IF J=0 THEN 1324 diff --git a/53_King/csharp/Country.cs b/53_King/csharp/Country.cs index b1b473e7..d1ba9235 100644 --- a/53_King/csharp/Country.cs +++ b/53_King/csharp/Country.cs @@ -37,6 +37,7 @@ internal class Country => Resource.Status(_rallods, _countrymen, _foreigners, _arableLand, landValue, plantingCost); public float Countrymen => _countrymen; + public bool HasWorkers => _foreigners > 0; private float FarmLand => _arableLand; public bool HasRallods => _rallods > 0; public float Rallods => _rallods; @@ -115,4 +116,10 @@ internal class Country } public void RemoveTheDead(int deaths) => _countrymen = (int)(_countrymen - deaths); + + public void Migration(int migration) => _countrymen = (int)(_countrymen + migration); + + public void AddWorkers(int newWorkers) => _foreigners = (int)(_foreigners + newWorkers); + + public void SellCrops(int income) => _rallods = (int)(_rallods + income); } diff --git a/53_King/csharp/Reign.cs b/53_King/csharp/Reign.cs index 727f71d3..1955c81b 100644 --- a/53_King/csharp/Reign.cs +++ b/53_King/csharp/Reign.cs @@ -24,7 +24,7 @@ internal class Reign public bool PlayYear() { - var year = new Year(_country, _random); + var year = new Year(_country, _random, _io); _io.Write(year.Status); @@ -32,7 +32,7 @@ internal class Reign { _io.WriteLine(); _io.WriteLine(); - year.EvaluateResults(_io, _random); + year.EvaluateResults(); _yearNumber++; return true; } diff --git a/53_King/csharp/Resources/Emigration.txt b/53_King/csharp/Resources/Emigration.txt new file mode 100644 index 00000000..01f67094 --- /dev/null +++ b/53_King/csharp/Resources/Emigration.txt @@ -0,0 +1 @@ + {0} countrymen left the island. \ No newline at end of file diff --git a/53_King/csharp/Resources/Harvest.txt b/53_King/csharp/Resources/Harvest.txt new file mode 100644 index 00000000..7cedf658 --- /dev/null +++ b/53_King/csharp/Resources/Harvest.txt @@ -0,0 +1,2 @@ + you harvested {0} sq. miles of crops. +{1}making {2} rallods. \ No newline at end of file diff --git a/53_King/csharp/Resources/HarvestReason.txt b/53_King/csharp/Resources/HarvestReason.txt new file mode 100644 index 00000000..82faccb5 --- /dev/null +++ b/53_King/csharp/Resources/HarvestReason.txt @@ -0,0 +1 @@ + (Due to increased air and water pollution from foreign industry.) diff --git a/53_King/csharp/Resources/Immigration.txt b/53_King/csharp/Resources/Immigration.txt new file mode 100644 index 00000000..30d8b6b3 --- /dev/null +++ b/53_King/csharp/Resources/Immigration.txt @@ -0,0 +1 @@ + {0} countrymen came to the island. \ No newline at end of file diff --git a/53_King/csharp/Resources/LandPlanted.txt b/53_King/csharp/Resources/LandPlanted.txt new file mode 100644 index 00000000..e52529aa --- /dev/null +++ b/53_King/csharp/Resources/LandPlanted.txt @@ -0,0 +1 @@ +Of {0} sq. miles planted, \ No newline at end of file diff --git a/53_King/csharp/Resources/Resource.cs b/53_King/csharp/Resources/Resource.cs index 496bc250..9fd56b8d 100644 --- a/53_King/csharp/Resources/Resource.cs +++ b/53_King/csharp/Resources/Resource.cs @@ -56,6 +56,18 @@ internal static class Resource public static string FuneralExpenses(int expenses) => string.Format(GetString(), expenses); public static string InsufficientReserves => GetString(); + public static string WorkerMigration(int newWorkers) => string.Format(GetString(), newWorkers); + public static string Migration(int migration) + => string.Format(migration < 0 ? Emigration : Immigration, Math.Abs(migration)); + public static string Emigration => GetString(); + public static string Immigration => GetString(); + + public static string LandPlanted(float landPlanted) + => landPlanted > 0 ? string.Format(GetString(), (int)landPlanted) : ""; + public static string Harvest(int yield, int income, bool hasIndustry) + => string.Format(GetString(), yield, HarvestReason(hasIndustry), income); + private static string HarvestReason(bool hasIndustry) => hasIndustry ? GetString() : ""; + private static string PollutionEffect(IRandom random) => GetStrings()[random.Next(5)]; private static string EndAlso(IRandom random) diff --git a/53_King/csharp/Resources/WorkerMigration.txt b/53_King/csharp/Resources/WorkerMigration.txt new file mode 100644 index 00000000..d2f3b378 --- /dev/null +++ b/53_King/csharp/Resources/WorkerMigration.txt @@ -0,0 +1 @@ + {0} workers came to the country and \ No newline at end of file diff --git a/53_King/csharp/Year.cs b/53_King/csharp/Year.cs index 18f208e4..526db454 100644 --- a/53_King/csharp/Year.cs +++ b/53_King/csharp/Year.cs @@ -6,6 +6,7 @@ internal class Year { private readonly Country _country; private readonly IRandom _random; + private readonly IReadWrite _io; private readonly int _plantingCost; private readonly int _landValue; @@ -14,7 +15,12 @@ internal class Year private float _landPlanted; private float _pollutionControlCost; - public Year(Country country, IRandom random) + private float _citizenSupport; + private int _deaths; + private int _pollutionDeaths; + + + public Year(Country country, IRandom random, IReadWrite io) { _country = country; _random = random; @@ -35,54 +41,81 @@ internal class Year return playerSoldLand || playerDistributedRallods || playerPlantedLand || playerControlledPollution; } - public Result EvaluateResults(IReadWrite io) + public Result EvaluateResults() { var unspentRallods = _country.Rallods; - var statusUpdate = new StringBuilder(); - var result = EvaluateDeaths(statusUpdate, out var deaths); - - io.Write(statusUpdate); + var result = EvaluateDeaths(); return Result.Continue; } - public Result? EvaluateDeaths(StringBuilder statusUpdate, out int deaths) + public Result? EvaluateDeaths() { - deaths = default; - var supportedCountrymen = _rallodsDistributed / 100; - var starvationDeaths = _country.Countrymen - supportedCountrymen; + _citizenSupport = supportedCountrymen - _country.Countrymen; + var starvationDeaths = -_citizenSupport; if (starvationDeaths > 0) { if (supportedCountrymen < 50) { return Result.GameOver(EndOneThirdDead(_random)); } - statusUpdate.AppendLine(DeathsStarvation(starvationDeaths)); + _io.WriteLine(DeathsStarvation(starvationDeaths)); } var pollutionControl = _pollutionControlCost >= 25 ? _pollutionControlCost / 25 : 1; - var pollutionDeaths = (int)(_random.Next((int)_country.IndustryLand) / pollutionControl); - if (pollutionDeaths > 0) + _pollutionDeaths = (int)(_random.Next((int)_country.IndustryLand) / pollutionControl); + if (_pollutionDeaths > 0) { - statusUpdate.AppendLine(DeathsPollution(pollutionDeaths)); + _io.WriteLine(DeathsPollution(_pollutionDeaths)); } - deaths = (int)(starvationDeaths + pollutionDeaths); - if (deaths > 0) + _deaths = (int)(starvationDeaths + _pollutionDeaths); + if (_deaths > 0) { - var funeralCosts = deaths * 9; - statusUpdate.AppendLine(FuneralExpenses(funeralCosts)); + var funeralCosts = _deaths * 9; + _io.WriteLine(FuneralExpenses(funeralCosts)); if (!_country.TrySpend(funeralCosts, _landValue)) { - statusUpdate.AppendLine(InsufficientReserves); + _io.WriteLine(InsufficientReserves); } - _country.RemoveTheDead(deaths); + _country.RemoveTheDead(_deaths); } return null; } + private Result? EvaluateMigration() + { + if (_landSold > 0) + { + var newWorkers = (int)(_landSold + _random.NextFloat(10) - _random.NextFloat(20)); + if (!_country.HasWorkers) { newWorkers += 20; } + _io.Write(WorkerMigration(newWorkers)); + _country.AddWorkers(newWorkers); + } + + var migration = + (int)(_citizenSupport / 10 + _pollutionControlCost / 25 - _country.IndustryLand / 50 - _pollutionDeaths / 2); + _io.WriteLine(Migration(migration)); + _country.Migration(migration); + + return null; + } + + private Result? EvaluateAgriculture() + { + var ruinedCrops = (int)Math.Min(_country.IndustryLand * (_random.NextFloat() + 1.5f) / 2, _landPlanted); + var yield = (int)(_landPlanted - ruinedCrops); + var income = (int)(yield * _landValue / 2f); + + _io.Write(LandPlanted(_landPlanted)); + _io.Write(Harvest(yield, income, _country.IndustryLand > 0)); + + _country.SellCrops(income); + + return null; + } internal record struct Result (bool IsGameOver, string Message) { From 62db3a9c9a7994d468a2238fc618be7532b71afb Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Sat, 21 Jan 2023 22:24:20 +1100 Subject: [PATCH 007/141] Finish game --- 53_King/csharp/Country.cs | 8 +++ 53_King/csharp/Game.cs | 6 +- 53_King/csharp/Reign.cs | 21 +++--- 53_King/csharp/Resources/Harvest.txt | 2 +- 53_King/csharp/Resources/Resource.cs | 7 +- 53_King/csharp/Resources/TourismDecrease.txt | 1 + 53_King/csharp/Resources/TourismEarnings.txt | 1 + ...{PollutionEffect.txt => TourismReason.txt} | 0 53_King/csharp/Result.cs | 7 ++ 53_King/csharp/Year.cs | 64 +++++++++++++------ 10 files changed, 81 insertions(+), 36 deletions(-) create mode 100644 53_King/csharp/Resources/TourismDecrease.txt create mode 100644 53_King/csharp/Resources/TourismEarnings.txt rename 53_King/csharp/Resources/{PollutionEffect.txt => TourismReason.txt} (100%) create mode 100644 53_King/csharp/Result.cs diff --git a/53_King/csharp/Country.cs b/53_King/csharp/Country.cs index d1ba9235..1a37bcd8 100644 --- a/53_King/csharp/Country.cs +++ b/53_King/csharp/Country.cs @@ -37,11 +37,13 @@ internal class Country => Resource.Status(_rallods, _countrymen, _foreigners, _arableLand, landValue, plantingCost); public float Countrymen => _countrymen; + public float Workers => _foreigners; public bool HasWorkers => _foreigners > 0; private float FarmLand => _arableLand; public bool HasRallods => _rallods > 0; public float Rallods => _rallods; public float IndustryLand => InitialLand - _arableLand; + public int PreviousTourismIncome { get; private set; } public bool SellLand(int landValue, out float landSold) { @@ -122,4 +124,10 @@ internal class Country public void AddWorkers(int newWorkers) => _foreigners = (int)(_foreigners + newWorkers); public void SellCrops(int income) => _rallods = (int)(_rallods + income); + + public void EntertainTourists(int income) + { + PreviousTourismIncome = income; + _rallods = (int)(_rallods + income); + } } diff --git a/53_King/csharp/Game.cs b/53_King/csharp/Game.cs index 92d8063f..6a91010c 100644 --- a/53_King/csharp/Game.cs +++ b/53_King/csharp/Game.cs @@ -15,7 +15,7 @@ internal class Game public void Play() { - _io.Write(Resource.Title); + _io.Write(Title); var reign = SetUpReign(); if (reign != null) @@ -29,7 +29,7 @@ internal class Game private Reign? SetUpReign() { - var response = _io.ReadString(Resource.InstructionsPrompt).ToUpper(); + var response = _io.ReadString(InstructionsPrompt).ToUpper(); if (response.Equals("Again", StringComparison.InvariantCultureIgnoreCase)) { @@ -38,7 +38,7 @@ internal class Game if (!response.StartsWith("N", StringComparison.InvariantCultureIgnoreCase)) { - _io.Write(Resource.InstructionsText(TermOfOffice)); + _io.Write(InstructionsText(TermOfOffice)); } _io.WriteLine(); diff --git a/53_King/csharp/Reign.cs b/53_King/csharp/Reign.cs index 1955c81b..7a96a30f 100644 --- a/53_King/csharp/Reign.cs +++ b/53_King/csharp/Reign.cs @@ -28,19 +28,18 @@ internal class Reign _io.Write(year.Status); - if (year.GetPlayerActions()) + var result = year.GetPlayerActions() ?? year.EvaluateResults() ?? IsAtEndOfTerm(); + if (result.IsGameOver) { - _io.WriteLine(); - _io.WriteLine(); - year.EvaluateResults(); - _yearNumber++; - return true; - } - else - { - _io.WriteLine(); - _io.Write(Goodbye); + _io.WriteLine(result.Message); return false; } + + return true; } + + private Result IsAtEndOfTerm() + => _yearNumber == MaxTerm + ? Result.GameOver(EndCongratulations(MaxTerm)) + : Result.Continue; } diff --git a/53_King/csharp/Resources/Harvest.txt b/53_King/csharp/Resources/Harvest.txt index 7cedf658..287e0c42 100644 --- a/53_King/csharp/Resources/Harvest.txt +++ b/53_King/csharp/Resources/Harvest.txt @@ -1,2 +1,2 @@ you harvested {0} sq. miles of crops. -{1}making {2} rallods. \ No newline at end of file +{1}making {2} rallods. diff --git a/53_King/csharp/Resources/Resource.cs b/53_King/csharp/Resources/Resource.cs index 9fd56b8d..5ea09e1c 100644 --- a/53_King/csharp/Resources/Resource.cs +++ b/53_King/csharp/Resources/Resource.cs @@ -68,7 +68,9 @@ internal static class Resource => string.Format(GetString(), yield, HarvestReason(hasIndustry), income); private static string HarvestReason(bool hasIndustry) => hasIndustry ? GetString() : ""; - private static string PollutionEffect(IRandom random) => GetStrings()[random.Next(5)]; + public static string TourismEarnings(int income) => string.Format(GetString(), income); + public static string TourismDecrease(IRandom random) => string.Format(GetString(), TourismReason(random)); + private static string TourismReason(IRandom random) => GetStrings()[random.Next(5)]; private static string EndAlso(IRandom random) => random.Next(10) switch @@ -82,7 +84,7 @@ internal static class Resource private static string EndConsequences(IRandom random) => GetStrings()[random.Next(2)]; public static string EndForeignWorkers(IRandom random) => string.Format(GetString(), EndConsequences(random)); public static string EndManyDead(int deaths, IRandom random) => string.Format(GetString(), deaths, EndAlso(random)); - public static string EndMoneyLeftOver(int termLength) => string.Format(GetString(), termLength); + public static string EndMoneyLeftOver() => GetString(); public static string EndOneThirdDead(IRandom random) => string.Format(GetString(), EndConsequences(random)); public static string SavedYearsPrompt => GetString(); @@ -104,7 +106,6 @@ internal static class Resource return reader.ReadToEnd(); } - private static Stream GetStream([CallerMemberName] string? name = null) => Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(Resource).Namespace}.{name}.txt") ?? throw new Exception($"Could not find embedded resource stream '{name}'."); diff --git a/53_King/csharp/Resources/TourismDecrease.txt b/53_King/csharp/Resources/TourismDecrease.txt new file mode 100644 index 00000000..dcb45fc7 --- /dev/null +++ b/53_King/csharp/Resources/TourismDecrease.txt @@ -0,0 +1 @@ + Decrease because {0} \ No newline at end of file diff --git a/53_King/csharp/Resources/TourismEarnings.txt b/53_King/csharp/Resources/TourismEarnings.txt new file mode 100644 index 00000000..db9251dd --- /dev/null +++ b/53_King/csharp/Resources/TourismEarnings.txt @@ -0,0 +1 @@ + You made {0} rallods from tourist trade. \ No newline at end of file diff --git a/53_King/csharp/Resources/PollutionEffect.txt b/53_King/csharp/Resources/TourismReason.txt similarity index 100% rename from 53_King/csharp/Resources/PollutionEffect.txt rename to 53_King/csharp/Resources/TourismReason.txt diff --git a/53_King/csharp/Result.cs b/53_King/csharp/Result.cs new file mode 100644 index 00000000..185f4617 --- /dev/null +++ b/53_King/csharp/Result.cs @@ -0,0 +1,7 @@ +namespace King; + +internal record struct Result (bool IsGameOver, string Message) +{ + internal static Result GameOver(string message) => new(true, message); + internal static Result Continue => new(false, ""); +} diff --git a/53_King/csharp/Year.cs b/53_King/csharp/Year.cs index 526db454..8e6bb0b4 100644 --- a/53_King/csharp/Year.cs +++ b/53_King/csharp/Year.cs @@ -17,13 +17,15 @@ internal class Year private float _citizenSupport; private int _deaths; + private float _starvationDeaths; private int _pollutionDeaths; - + private int _migration; public Year(Country country, IRandom random, IReadWrite io) { _country = country; _random = random; + _io = io; _plantingCost = random.Next(10, 15); _landValue = random.Next(95, 105); @@ -31,34 +33,41 @@ internal class Year public string Status => _country.GetStatus(_landValue, _plantingCost); - public bool GetPlayerActions() + public Result? GetPlayerActions() { var playerSoldLand = _country.SellLand(_landValue, out _landSold); var playerDistributedRallods = _country.DistributeRallods(out _rallodsDistributed); var playerPlantedLand = _country.HasRallods && _country.PlantLand(_plantingCost, out _landPlanted); var playerControlledPollution = _country.HasRallods && _country.ControlPollution(out _pollutionControlCost); - return playerSoldLand || playerDistributedRallods || playerPlantedLand || playerControlledPollution; + return playerSoldLand || playerDistributedRallods || playerPlantedLand || playerControlledPollution + ? null + : Result.GameOver(Goodbye); } - public Result EvaluateResults() + public Result? EvaluateResults() { - var unspentRallods = _country.Rallods; + var rallodsUnspent = _country.Rallods; - var result = EvaluateDeaths(); + _io.WriteLine(); + _io.WriteLine(); - return Result.Continue; + return EvaluateDeaths() + ?? EvaluateMigration() + ?? EvaluateAgriculture() + ?? EvaluateTourism() + ?? DetermineResult(rallodsUnspent); } public Result? EvaluateDeaths() { var supportedCountrymen = _rallodsDistributed / 100; _citizenSupport = supportedCountrymen - _country.Countrymen; - var starvationDeaths = -_citizenSupport; - if (starvationDeaths > 0) + _starvationDeaths = -_citizenSupport; + if (_starvationDeaths > 0) { if (supportedCountrymen < 50) { return Result.GameOver(EndOneThirdDead(_random)); } - _io.WriteLine(DeathsStarvation(starvationDeaths)); + _io.WriteLine(DeathsStarvation(_starvationDeaths)); } var pollutionControl = _pollutionControlCost >= 25 ? _pollutionControlCost / 25 : 1; @@ -68,7 +77,7 @@ internal class Year _io.WriteLine(DeathsPollution(_pollutionDeaths)); } - _deaths = (int)(starvationDeaths + _pollutionDeaths); + _deaths = (int)(_starvationDeaths + _pollutionDeaths); if (_deaths > 0) { var funeralCosts = _deaths * 9; @@ -95,10 +104,10 @@ internal class Year _country.AddWorkers(newWorkers); } - var migration = + _migration = (int)(_citizenSupport / 10 + _pollutionControlCost / 25 - _country.IndustryLand / 50 - _pollutionDeaths / 2); - _io.WriteLine(Migration(migration)); - _country.Migration(migration); + _io.WriteLine(Migration(_migration)); + _country.Migration(_migration); return null; } @@ -117,10 +126,29 @@ internal class Year return null; } - internal record struct Result (bool IsGameOver, string Message) + private Result? EvaluateTourism() { - internal static Result GameOver(string message) => new(true, message); - internal static Result Continue => new(false, ""); + var reputationValue = (int)((_country.Countrymen - _migration) * 22 + _random.NextFloat(500)); + var industryAdjustment = (int)(_country.IndustryLand * 15); + var tourismIncome = Math.Abs(reputationValue - industryAdjustment); + + _io.WriteLine(TourismEarnings(tourismIncome)); + if (industryAdjustment > 0 && tourismIncome < _country.PreviousTourismIncome) + { + _io.Write(TourismDecrease(_random)); + } + + _country.EntertainTourists(tourismIncome); + + return null; + } + + private Result? DetermineResult(float rallodsUnspent) + { + if (_deaths > 200) { return Result.GameOver(EndManyDead(_deaths, _random)); } + if (_country.Countrymen < 343) { return Result.GameOver(EndOneThirdDead(_random)); } + if (rallodsUnspent / 100 > 5 && _starvationDeaths >= 2) { return Result.GameOver(EndMoneyLeftOver()); } + if (_country.Workers > _country.Countrymen) { return Result.GameOver(EndForeignWorkers(_random)); } + return null; } } - From f6bde494744cbcff2bb46c8656ab3c2e9763dc2c Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Tue, 24 Jan 2023 07:40:15 +1100 Subject: [PATCH 008/141] Configure project --- 72_Queen/csharp/Queen.csproj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/72_Queen/csharp/Queen.csproj b/72_Queen/csharp/Queen.csproj index d3fe4757..3870320c 100644 --- a/72_Queen/csharp/Queen.csproj +++ b/72_Queen/csharp/Queen.csproj @@ -6,4 +6,12 @@ enable enable + + + + + + + + From 083a11f42ffbe9ddb95a864115099cb2d8170269 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Tue, 24 Jan 2023 17:47:34 +1100 Subject: [PATCH 009/141] Add string resources --- 72_Queen/csharp/Program.cs | 3 ++ 72_Queen/csharp/Resources/AnyonePrompt.txt | 1 + 72_Queen/csharp/Resources/ComputerMove.txt | 1 + 72_Queen/csharp/Resources/Congratulations.txt | 7 +++ 72_Queen/csharp/Resources/Forfeit.txt | 2 + 72_Queen/csharp/Resources/IWin.txt | 4 ++ 72_Queen/csharp/Resources/IllegalMove.txt | 2 + 72_Queen/csharp/Resources/IllegalStart.txt | 3 ++ 72_Queen/csharp/Resources/Instructions.txt | 16 +++++++ .../csharp/Resources/InstructionsPrompt.txt | 1 + 72_Queen/csharp/Resources/MovePrompt.txt | 1 + 72_Queen/csharp/Resources/Resource.cs | 47 +++++++++++++++++++ 72_Queen/csharp/Resources/StartPrompt.txt | 1 + 72_Queen/csharp/Resources/Thanks.txt | 1 + 72_Queen/csharp/Resources/Title.txt | 5 ++ 72_Queen/csharp/Resources/YesyOrNo.txt | 1 + 16 files changed, 96 insertions(+) create mode 100644 72_Queen/csharp/Program.cs create mode 100644 72_Queen/csharp/Resources/AnyonePrompt.txt create mode 100644 72_Queen/csharp/Resources/ComputerMove.txt create mode 100644 72_Queen/csharp/Resources/Congratulations.txt create mode 100644 72_Queen/csharp/Resources/Forfeit.txt create mode 100644 72_Queen/csharp/Resources/IWin.txt create mode 100644 72_Queen/csharp/Resources/IllegalMove.txt create mode 100644 72_Queen/csharp/Resources/IllegalStart.txt create mode 100644 72_Queen/csharp/Resources/Instructions.txt create mode 100644 72_Queen/csharp/Resources/InstructionsPrompt.txt create mode 100644 72_Queen/csharp/Resources/MovePrompt.txt create mode 100644 72_Queen/csharp/Resources/Resource.cs create mode 100644 72_Queen/csharp/Resources/StartPrompt.txt create mode 100644 72_Queen/csharp/Resources/Thanks.txt create mode 100644 72_Queen/csharp/Resources/Title.txt create mode 100644 72_Queen/csharp/Resources/YesyOrNo.txt diff --git a/72_Queen/csharp/Program.cs b/72_Queen/csharp/Program.cs new file mode 100644 index 00000000..0733c377 --- /dev/null +++ b/72_Queen/csharp/Program.cs @@ -0,0 +1,3 @@ +using Games.Common.IO; + +var io = new ConsoleIO(); \ No newline at end of file diff --git a/72_Queen/csharp/Resources/AnyonePrompt.txt b/72_Queen/csharp/Resources/AnyonePrompt.txt new file mode 100644 index 00000000..a0289fd6 --- /dev/null +++ b/72_Queen/csharp/Resources/AnyonePrompt.txt @@ -0,0 +1 @@ +Anyone else care to try \ No newline at end of file diff --git a/72_Queen/csharp/Resources/ComputerMove.txt b/72_Queen/csharp/Resources/ComputerMove.txt new file mode 100644 index 00000000..f31b8e32 --- /dev/null +++ b/72_Queen/csharp/Resources/ComputerMove.txt @@ -0,0 +1 @@ +Computer moves to square {0} diff --git a/72_Queen/csharp/Resources/Congratulations.txt b/72_Queen/csharp/Resources/Congratulations.txt new file mode 100644 index 00000000..9f1db232 --- /dev/null +++ b/72_Queen/csharp/Resources/Congratulations.txt @@ -0,0 +1,7 @@ + +C O N G R A T U L A T I O N S . . . + +You have won--very well played. +It looks like I have met my match. +Thanks for playing--I can't win all the time. + diff --git a/72_Queen/csharp/Resources/Forfeit.txt b/72_Queen/csharp/Resources/Forfeit.txt new file mode 100644 index 00000000..120da75d --- /dev/null +++ b/72_Queen/csharp/Resources/Forfeit.txt @@ -0,0 +1,2 @@ +Looks like I have won by forfeit. + diff --git a/72_Queen/csharp/Resources/IWin.txt b/72_Queen/csharp/Resources/IWin.txt new file mode 100644 index 00000000..ced5d716 --- /dev/null +++ b/72_Queen/csharp/Resources/IWin.txt @@ -0,0 +1,4 @@ + +Nice try, but it looks like I have won. +Thanks for playing. + diff --git a/72_Queen/csharp/Resources/IllegalMove.txt b/72_Queen/csharp/Resources/IllegalMove.txt new file mode 100644 index 00000000..3d31638d --- /dev/null +++ b/72_Queen/csharp/Resources/IllegalMove.txt @@ -0,0 +1,2 @@ + +Y O U C H E A T . . . Try again diff --git a/72_Queen/csharp/Resources/IllegalStart.txt b/72_Queen/csharp/Resources/IllegalStart.txt new file mode 100644 index 00000000..25402bb6 --- /dev/null +++ b/72_Queen/csharp/Resources/IllegalStart.txt @@ -0,0 +1,3 @@ +Please read the instructions again. +You have begun illegally. + diff --git a/72_Queen/csharp/Resources/Instructions.txt b/72_Queen/csharp/Resources/Instructions.txt new file mode 100644 index 00000000..d440b335 --- /dev/null +++ b/72_Queen/csharp/Resources/Instructions.txt @@ -0,0 +1,16 @@ +We are going to play a game based on one of the chess +moves. Our queen will be able to move only to the left, +down, or diagonally down and to the left. + +The object of the game is to place the queen in the lower +left hand square by alternating moves between you and the +computer. The first one to place the queen there wins. + +You go first and place the queen in any one of the squares +on the top row or right hand column. +That will be your first move. +We alternate moves. +You may forfeit by typing '0' as your move. +Be sure to press the return key after each response. + + diff --git a/72_Queen/csharp/Resources/InstructionsPrompt.txt b/72_Queen/csharp/Resources/InstructionsPrompt.txt new file mode 100644 index 00000000..0d311b60 --- /dev/null +++ b/72_Queen/csharp/Resources/InstructionsPrompt.txt @@ -0,0 +1 @@ +Do you want instructions \ No newline at end of file diff --git a/72_Queen/csharp/Resources/MovePrompt.txt b/72_Queen/csharp/Resources/MovePrompt.txt new file mode 100644 index 00000000..8cb18999 --- /dev/null +++ b/72_Queen/csharp/Resources/MovePrompt.txt @@ -0,0 +1 @@ +What is your move \ No newline at end of file diff --git a/72_Queen/csharp/Resources/Resource.cs b/72_Queen/csharp/Resources/Resource.cs new file mode 100644 index 00000000..5290766e --- /dev/null +++ b/72_Queen/csharp/Resources/Resource.cs @@ -0,0 +1,47 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Queen.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream Title => GetStream(); + public static Stream Instructions => GetStream(); + public static Stream YesOrNo => GetStream(); + public static Stream IllegalStart => GetStream(); + public static Stream ComputerMove => GetStream(); + public static Stream IllegalMove => GetStream(); + public static Stream Forfeit => GetStream(); + public static Stream IWin => GetStream(); + public static Stream Congratulations => GetStream(); + public static Stream Thanks => GetStream(); + } + + internal static class Prompts + { + public static string Instructions => GetPrompt(); + public static string Start => GetPrompt(); + public static string Move => GetPrompt(); + public static string Anyone => GetPrompt(); + } + + internal static class Formats + { + public static string Balance => GetString(); + } + + private static string GetPrompt([CallerMemberName] string? name = null) => GetString($"{name}Prompt"); + + private static string GetString([CallerMemberName] string? name = null) + { + using var stream = GetStream(name); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + private static Stream GetStream([CallerMemberName] string? name = null) => + Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(Resource).Namespace}.{name}.txt") + ?? throw new Exception($"Could not find embedded resource stream '{name}'."); +} \ No newline at end of file diff --git a/72_Queen/csharp/Resources/StartPrompt.txt b/72_Queen/csharp/Resources/StartPrompt.txt new file mode 100644 index 00000000..0b6f395b --- /dev/null +++ b/72_Queen/csharp/Resources/StartPrompt.txt @@ -0,0 +1 @@ +Where would you like to start \ No newline at end of file diff --git a/72_Queen/csharp/Resources/Thanks.txt b/72_Queen/csharp/Resources/Thanks.txt new file mode 100644 index 00000000..53980b09 --- /dev/null +++ b/72_Queen/csharp/Resources/Thanks.txt @@ -0,0 +1 @@ +Ok --- thanks again. \ No newline at end of file diff --git a/72_Queen/csharp/Resources/Title.txt b/72_Queen/csharp/Resources/Title.txt new file mode 100644 index 00000000..63549d09 --- /dev/null +++ b/72_Queen/csharp/Resources/Title.txt @@ -0,0 +1,5 @@ + Queen + Creative Computing Morristown, New Jersey + + + diff --git a/72_Queen/csharp/Resources/YesyOrNo.txt b/72_Queen/csharp/Resources/YesyOrNo.txt new file mode 100644 index 00000000..657bf8aa --- /dev/null +++ b/72_Queen/csharp/Resources/YesyOrNo.txt @@ -0,0 +1 @@ +Please answer 'Yes' or 'No'. From 29636d5696c8fb97464a6c4d0a5d3add7df1a709 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Wed, 25 Jan 2023 23:01:36 +1100 Subject: [PATCH 010/141] CSHARP-72 Add game intro --- 72_Queen/csharp/Games.cs | 34 +++++++++++++++++++ 72_Queen/csharp/Program.cs | 8 +++-- .../Resources/{YesyOrNo.txt => YesOrNo.txt} | 0 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 72_Queen/csharp/Games.cs rename 72_Queen/csharp/Resources/{YesyOrNo.txt => YesOrNo.txt} (100%) diff --git a/72_Queen/csharp/Games.cs b/72_Queen/csharp/Games.cs new file mode 100644 index 00000000..73a0b80b --- /dev/null +++ b/72_Queen/csharp/Games.cs @@ -0,0 +1,34 @@ +namespace Queen; + +internal class Game +{ + private readonly IReadWrite _io; + private readonly IRandom _random; + + public Game(IReadWrite io, IRandom random) + { + _io = io; + _random = random; + } + + internal void Play() + { + _io.Write(Streams.Title); + if (_io.ShouldDisplayInstructions()) { _io.Write(Streams.Instructions); } + } +} + +internal static class IOExtensions +{ + internal static bool ShouldDisplayInstructions(this IReadWrite io) + { + while (true) + { + var answer = io.ReadString(Prompts.Instructions).ToLower(); + if (answer == "yes") { return true; } + if (answer == "no") { return false; } + + io.Write(Streams.YesOrNo); + } + } +} diff --git a/72_Queen/csharp/Program.cs b/72_Queen/csharp/Program.cs index 0733c377..a32aded0 100644 --- a/72_Queen/csharp/Program.cs +++ b/72_Queen/csharp/Program.cs @@ -1,3 +1,7 @@ -using Games.Common.IO; +global using Games.Common.IO; +global using Games.Common.Randomness; +global using static Queen.Resources.Resource; -var io = new ConsoleIO(); \ No newline at end of file +using Queen; + +new Game(new ConsoleIO(), new RandomNumberGenerator()).Play(); \ No newline at end of file diff --git a/72_Queen/csharp/Resources/YesyOrNo.txt b/72_Queen/csharp/Resources/YesOrNo.txt similarity index 100% rename from 72_Queen/csharp/Resources/YesyOrNo.txt rename to 72_Queen/csharp/Resources/YesOrNo.txt From 1c911e9b3055469c6ed1a1e7aad490fbb6a5fbf7 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Fri, 27 Jan 2023 08:32:16 +1100 Subject: [PATCH 011/141] Add replay loop --- 72_Queen/csharp/Games.cs | 72 +++++++++++++++++++++++++-- 72_Queen/csharp/Program.cs | 2 +- 72_Queen/csharp/Resources/Board.txt | 26 ++++++++++ 72_Queen/csharp/Resources/Resource.cs | 1 + 4 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 72_Queen/csharp/Resources/Board.txt diff --git a/72_Queen/csharp/Games.cs b/72_Queen/csharp/Games.cs index 73a0b80b..4504c771 100644 --- a/72_Queen/csharp/Games.cs +++ b/72_Queen/csharp/Games.cs @@ -1,11 +1,11 @@ namespace Queen; -internal class Game +internal class Games { private readonly IReadWrite _io; private readonly IRandom _random; - public Game(IReadWrite io, IRandom random) + public Games(IReadWrite io, IRandom random) { _io = io; _random = random; @@ -14,21 +14,83 @@ internal class Game internal void Play() { _io.Write(Streams.Title); - if (_io.ShouldDisplayInstructions()) { _io.Write(Streams.Instructions); } + if (_io.ReadYesNo(Prompts.Instructions)) { _io.Write(Streams.Instructions); } + + while (true) + { + PlayGame(); + + if (!_io.ReadYesNo(Prompts.Anyone)) + { + _io.Write(Streams.Thanks); + return; + } + } + } + + internal void PlayGame() + { + _io.Write(Streams.Board); + var humanPosition = _io.ReadPosition(Prompts.Start, p => p.IsStart, Streams.IllegalStart, repeatPrompt: true) + if (humanPosition.IsZero) + { + _io.Write(Streams.Forfeit); + return; + } + + } } internal static class IOExtensions { - internal static bool ShouldDisplayInstructions(this IReadWrite io) + internal static bool ReadYesNo(this IReadWrite io, string prompt) { while (true) { - var answer = io.ReadString(Prompts.Instructions).ToLower(); + var answer = io.ReadString(prompt).ToLower(); if (answer == "yes") { return true; } if (answer == "no") { return false; } io.Write(Streams.YesOrNo); } } + + internal static Position ReadPosition( + this IReadWrite io, + string prompt, + Predicate isValid, + Stream error, + bool repeatPrompt = false) + { + while (true) + { + var response = io.ReadNumber(prompt); + var number = (int)response; + var position = new Position(number); + if (number == response && (position.IsZero || isValid(position))) + { + return position; + } + + io.Write(error); + if (!repeatPrompt) { prompt = ""; } + } + } } + +internal record struct Position(int Diagonal, int Row) +{ + public static readonly Position Zero = new(0); + + public Position(int number) + : this(Diagonal: number / 10, Row: number % 10) + { + } + + public bool IsZero => Row == 0 && Diagonal == 0; + public bool IsStart => Row == 1 || Row == Diagonal; + public bool IsEnd => Row == 8 && Diagonal == 15; + + public override string ToString() => $"{Diagonal}{Row}"; +} \ No newline at end of file diff --git a/72_Queen/csharp/Program.cs b/72_Queen/csharp/Program.cs index a32aded0..a03fe364 100644 --- a/72_Queen/csharp/Program.cs +++ b/72_Queen/csharp/Program.cs @@ -4,4 +4,4 @@ global using static Queen.Resources.Resource; using Queen; -new Game(new ConsoleIO(), new RandomNumberGenerator()).Play(); \ No newline at end of file +new Games(new ConsoleIO(), new RandomNumberGenerator()).Play(); \ No newline at end of file diff --git a/72_Queen/csharp/Resources/Board.txt b/72_Queen/csharp/Resources/Board.txt new file mode 100644 index 00000000..45a8ab0a --- /dev/null +++ b/72_Queen/csharp/Resources/Board.txt @@ -0,0 +1,26 @@ + + 81 71 61 51 41 31 21 11 + + + 92 82 72 62 52 42 32 22 + + + 103 93 83 73 63 53 43 33 + + + 114 104 94 84 74 64 54 44 + + + 125 115 105 95 85 75 65 55 + + + 136 126 116 106 96 86 76 66 + + + 147 137 127 117 107 97 87 77 + + + 158 148 138 128 118 108 98 88 + + + diff --git a/72_Queen/csharp/Resources/Resource.cs b/72_Queen/csharp/Resources/Resource.cs index 5290766e..9ff43842 100644 --- a/72_Queen/csharp/Resources/Resource.cs +++ b/72_Queen/csharp/Resources/Resource.cs @@ -10,6 +10,7 @@ internal static class Resource public static Stream Title => GetStream(); public static Stream Instructions => GetStream(); public static Stream YesOrNo => GetStream(); + public static Stream Board => GetStream(); public static Stream IllegalStart => GetStream(); public static Stream ComputerMove => GetStream(); public static Stream IllegalMove => GetStream(); From 3e88424a5216963e5d0c7c548aae21567d1a4ce0 Mon Sep 17 00:00:00 2001 From: Andrew Cooper Date: Sat, 28 Jan 2023 15:25:52 +1100 Subject: [PATCH 012/141] Add computer strategy --- 72_Queen/csharp/Computer.cs | 0 72_Queen/csharp/Game.cs | 164 +++++++++++++++++++++ 72_Queen/csharp/Games.cs | 96 ------------ 72_Queen/csharp/Program.cs | 2 +- 72_Queen/csharp/Resources/Board.txt | 1 + 72_Queen/csharp/Resources/Forfeit.txt | 3 +- 72_Queen/csharp/Resources/Instructions.txt | 1 - 72_Queen/csharp/Resources/Thanks.txt | 2 + 8 files changed, 170 insertions(+), 99 deletions(-) create mode 100644 72_Queen/csharp/Computer.cs create mode 100644 72_Queen/csharp/Game.cs delete mode 100644 72_Queen/csharp/Games.cs diff --git a/72_Queen/csharp/Computer.cs b/72_Queen/csharp/Computer.cs new file mode 100644 index 00000000..e69de29b diff --git a/72_Queen/csharp/Game.cs b/72_Queen/csharp/Game.cs new file mode 100644 index 00000000..220e4535 --- /dev/null +++ b/72_Queen/csharp/Game.cs @@ -0,0 +1,164 @@ +namespace Queen; + +internal class Game +{ + private readonly IReadWrite _io; + private readonly IRandom _random; + private readonly Computer _computer; + + public Game(IReadWrite io, IRandom random) + { + _io = io; + _random = random; + _computer = new Computer(random); + } + + internal void PlaySeries() + { + _io.Write(Streams.Title); + if (_io.ReadYesNo(Prompts.Instructions)) { _io.Write(Streams.Instructions); } + + while (true) + { + var result = PlayGame(); + _io.Write(result switch + { + Result.HumanForfeits => Streams.Forfeit, + Result.HumanWins => Streams.Congratulations, + Result.ComputerWins => Streams.IWin, + _ => throw new InvalidOperationException($"Unexpected result {result}") + }); + + if (!_io.ReadYesNo(Prompts.Anyone)) { break; } + } + + _io.Write(Streams.Thanks); + } + + private Result PlayGame() + { + _io.Write(Streams.Board); + var humanPosition = _io.ReadPosition(Prompts.Start, p => p.IsStart, Streams.IllegalStart, repeatPrompt: true); + if (humanPosition.IsZero) { return Result.HumanForfeits; } + + while (true) + { + var computerPosition = _computer.GetMove(humanPosition); + if (computerPosition.IsEnd) { return Result.ComputerWins; } + } + + } + + private enum Result { ComputerWins, HumanWins, HumanForfeits }; +} + +internal class Computer +{ + private static readonly HashSet _randomiseFrom = new() { 41, 44, 73, 75, 126, 127 }; + private static readonly HashSet _desirable = new() { 73, 75, 126, 127, 158 }; + private readonly IRandom _random; + + public Computer(IRandom random) + { + _random = random; + } + + public Position GetMove(Position from) + => from + (_randomiseFrom.Contains(from) ? _random.NextMove() : FindMove(from)); + + private Move FindMove(Position from) + { + for (int i = 7; i > 0; i--) + { + if (IsOptimal(Move.Left, out var move)) { return move; } + if (IsOptimal(Move.Down, out move)) { return move; } + if (IsOptimal(Move.DownLeft, out move)) { return move; } + + bool IsOptimal(Move direction, out Move move) + { + move = direction * i; + return _desirable.Contains(from + move); + } + } + + return _random.NextMove(); + } +} + +internal static class IOExtensions +{ + internal static bool ReadYesNo(this IReadWrite io, string prompt) + { + while (true) + { + var answer = io.ReadString(prompt).ToLower(); + if (answer == "yes") { return true; } + if (answer == "no") { return false; } + + io.Write(Streams.YesOrNo); + } + } + + internal static Position ReadPosition( + this IReadWrite io, + string prompt, + Predicate isValid, + Stream error, + bool repeatPrompt = false) + { + while (true) + { + var response = io.ReadNumber(prompt); + var number = (int)response; + var position = new Position(number); + if (number == response && (position.IsZero || isValid(position))) + { + return position; + } + + io.Write(error); + if (!repeatPrompt) { prompt = ""; } + } + } +} + +internal record struct Position(int Diagonal, int Row) +{ + public static readonly Position Zero = new(0); + + public Position(int number) + : this(Diagonal: number / 10, Row: number % 10) + { + } + + public bool IsZero => Row == 0 && Diagonal == 0; + public bool IsStart => Row == 1 || Row == Diagonal; + public bool IsEnd => Row == 8 && Diagonal == 15; + + public override string ToString() => $"{Diagonal}{Row}"; + + public static implicit operator Position(int value) => new(value); + + public static Position operator +(Position position, Move move) + => new(Diagonal: position.Diagonal + move.Diagonal, Row: position.Row + move.Row); +} + +internal static class RandomExtensions +{ + internal static Move NextMove(this IRandom random) + => random.NextFloat() switch + { + > 0.6F => Move.Down, + > 0.3F => Move.DownLeft, + _ => Move.Left + }; +} + +internal record struct Move(int Diagonal, int Row) +{ + public static readonly Move Left = new(1, 0); + public static readonly Move DownLeft = new(2, 1); + public static readonly Move Down = new(1, 1); + + public static Move operator *(Move move, int scale) => new(move.Diagonal * scale, move.Row * scale); +} \ No newline at end of file diff --git a/72_Queen/csharp/Games.cs b/72_Queen/csharp/Games.cs deleted file mode 100644 index 4504c771..00000000 --- a/72_Queen/csharp/Games.cs +++ /dev/null @@ -1,96 +0,0 @@ -namespace Queen; - -internal class Games -{ - private readonly IReadWrite _io; - private readonly IRandom _random; - - public Games(IReadWrite io, IRandom random) - { - _io = io; - _random = random; - } - - internal void Play() - { - _io.Write(Streams.Title); - if (_io.ReadYesNo(Prompts.Instructions)) { _io.Write(Streams.Instructions); } - - while (true) - { - PlayGame(); - - if (!_io.ReadYesNo(Prompts.Anyone)) - { - _io.Write(Streams.Thanks); - return; - } - } - } - - internal void PlayGame() - { - _io.Write(Streams.Board); - var humanPosition = _io.ReadPosition(Prompts.Start, p => p.IsStart, Streams.IllegalStart, repeatPrompt: true) - if (humanPosition.IsZero) - { - _io.Write(Streams.Forfeit); - return; - } - - - } -} - -internal static class IOExtensions -{ - internal static bool ReadYesNo(this IReadWrite io, string prompt) - { - while (true) - { - var answer = io.ReadString(prompt).ToLower(); - if (answer == "yes") { return true; } - if (answer == "no") { return false; } - - io.Write(Streams.YesOrNo); - } - } - - internal static Position ReadPosition( - this IReadWrite io, - string prompt, - Predicate isValid, - Stream error, - bool repeatPrompt = false) - { - while (true) - { - var response = io.ReadNumber(prompt); - var number = (int)response; - var position = new Position(number); - if (number == response && (position.IsZero || isValid(position))) - { - return position; - } - - io.Write(error); - if (!repeatPrompt) { prompt = ""; } - } - } -} - -internal record struct Position(int Diagonal, int Row) -{ - public static readonly Position Zero = new(0); - - public Position(int number) - : this(Diagonal: number / 10, Row: number % 10) - { - } - - public bool IsZero => Row == 0 && Diagonal == 0; - public bool IsStart => Row == 1 || Row == Diagonal; - public bool IsEnd => Row == 8 && Diagonal == 15; - - public override string ToString() => $"{Diagonal}{Row}"; -} \ No newline at end of file diff --git a/72_Queen/csharp/Program.cs b/72_Queen/csharp/Program.cs index a03fe364..5e017df6 100644 --- a/72_Queen/csharp/Program.cs +++ b/72_Queen/csharp/Program.cs @@ -4,4 +4,4 @@ global using static Queen.Resources.Resource; using Queen; -new Games(new ConsoleIO(), new RandomNumberGenerator()).Play(); \ No newline at end of file +new Game(new ConsoleIO(), new RandomNumberGenerator()).PlaySeries(); \ No newline at end of file diff --git a/72_Queen/csharp/Resources/Board.txt b/72_Queen/csharp/Resources/Board.txt index 45a8ab0a..854be1bb 100644 --- a/72_Queen/csharp/Resources/Board.txt +++ b/72_Queen/csharp/Resources/Board.txt @@ -1,4 +1,5 @@ + 81 71 61 51 41 31 21 11 diff --git a/72_Queen/csharp/Resources/Forfeit.txt b/72_Queen/csharp/Resources/Forfeit.txt index 120da75d..09858bc1 100644 --- a/72_Queen/csharp/Resources/Forfeit.txt +++ b/72_Queen/csharp/Resources/Forfeit.txt @@ -1,2 +1,3 @@ -Looks like I have won by forfeit. + +It looks like I have won by forfeit. diff --git a/72_Queen/csharp/Resources/Instructions.txt b/72_Queen/csharp/Resources/Instructions.txt index d440b335..fc2e85b0 100644 --- a/72_Queen/csharp/Resources/Instructions.txt +++ b/72_Queen/csharp/Resources/Instructions.txt @@ -13,4 +13,3 @@ We alternate moves. You may forfeit by typing '0' as your move. Be sure to press the return key after each response. - diff --git a/72_Queen/csharp/Resources/Thanks.txt b/72_Queen/csharp/Resources/Thanks.txt index 53980b09..2e2e7b63 100644 --- a/72_Queen/csharp/Resources/Thanks.txt +++ b/72_Queen/csharp/Resources/Thanks.txt @@ -1 +1,3 @@ + + Ok --- thanks again. \ No newline at end of file From 3f2dd9f0a90c86307cf2da0b9038d7021d653ae7 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Sat, 28 Jan 2023 16:30:49 +1100 Subject: [PATCH 013/141] Complete game --- 72_Queen/csharp/Computer.cs | 34 +++++++ 72_Queen/csharp/Game.cs | 117 +--------------------- 72_Queen/csharp/IOExtensions.cs | 38 +++++++ 72_Queen/csharp/Move.cs | 15 +++ 72_Queen/csharp/Position.cs | 24 +++++ 72_Queen/csharp/RandomExtensions.cs | 12 +++ 72_Queen/csharp/Resources/IllegalMove.txt | 2 +- 72_Queen/csharp/Resources/Resource.cs | 5 +- 8 files changed, 131 insertions(+), 116 deletions(-) create mode 100644 72_Queen/csharp/IOExtensions.cs create mode 100644 72_Queen/csharp/Move.cs create mode 100644 72_Queen/csharp/Position.cs create mode 100644 72_Queen/csharp/RandomExtensions.cs diff --git a/72_Queen/csharp/Computer.cs b/72_Queen/csharp/Computer.cs index e69de29b..459c7337 100644 --- a/72_Queen/csharp/Computer.cs +++ b/72_Queen/csharp/Computer.cs @@ -0,0 +1,34 @@ +namespace Queen; + +internal class Computer +{ + private static readonly HashSet _randomiseFrom = new() { 41, 44, 73, 75, 126, 127 }; + private static readonly HashSet _desirable = new() { 73, 75, 126, 127, 158 }; + private readonly IRandom _random; + + public Computer(IRandom random) + { + _random = random; + } + + public Position GetMove(Position from) + => from + (_randomiseFrom.Contains(from) ? _random.NextMove() : FindMove(from)); + + private Move FindMove(Position from) + { + for (int i = 7; i > 0; i--) + { + if (IsOptimal(Move.Left, out var move)) { return move; } + if (IsOptimal(Move.Down, out move)) { return move; } + if (IsOptimal(Move.DownLeft, out move)) { return move; } + + bool IsOptimal(Move direction, out Move move) + { + move = direction * i; + return _desirable.Contains(from + move); + } + } + + return _random.NextMove(); + } +} diff --git a/72_Queen/csharp/Game.cs b/72_Queen/csharp/Game.cs index 220e4535..35b1dc9a 100644 --- a/72_Queen/csharp/Game.cs +++ b/72_Queen/csharp/Game.cs @@ -44,121 +44,14 @@ internal class Game while (true) { var computerPosition = _computer.GetMove(humanPosition); + _io.Write(Strings.ComputerMove(computerPosition)); if (computerPosition.IsEnd) { return Result.ComputerWins; } - } + humanPosition = _io.ReadPosition(Prompts.Move, p => (p - computerPosition).IsValid, Streams.IllegalMove); + if (humanPosition.IsZero) { return Result.HumanForfeits; } + if (humanPosition.IsEnd) { return Result.HumanWins; } + } } private enum Result { ComputerWins, HumanWins, HumanForfeits }; } - -internal class Computer -{ - private static readonly HashSet _randomiseFrom = new() { 41, 44, 73, 75, 126, 127 }; - private static readonly HashSet _desirable = new() { 73, 75, 126, 127, 158 }; - private readonly IRandom _random; - - public Computer(IRandom random) - { - _random = random; - } - - public Position GetMove(Position from) - => from + (_randomiseFrom.Contains(from) ? _random.NextMove() : FindMove(from)); - - private Move FindMove(Position from) - { - for (int i = 7; i > 0; i--) - { - if (IsOptimal(Move.Left, out var move)) { return move; } - if (IsOptimal(Move.Down, out move)) { return move; } - if (IsOptimal(Move.DownLeft, out move)) { return move; } - - bool IsOptimal(Move direction, out Move move) - { - move = direction * i; - return _desirable.Contains(from + move); - } - } - - return _random.NextMove(); - } -} - -internal static class IOExtensions -{ - internal static bool ReadYesNo(this IReadWrite io, string prompt) - { - while (true) - { - var answer = io.ReadString(prompt).ToLower(); - if (answer == "yes") { return true; } - if (answer == "no") { return false; } - - io.Write(Streams.YesOrNo); - } - } - - internal static Position ReadPosition( - this IReadWrite io, - string prompt, - Predicate isValid, - Stream error, - bool repeatPrompt = false) - { - while (true) - { - var response = io.ReadNumber(prompt); - var number = (int)response; - var position = new Position(number); - if (number == response && (position.IsZero || isValid(position))) - { - return position; - } - - io.Write(error); - if (!repeatPrompt) { prompt = ""; } - } - } -} - -internal record struct Position(int Diagonal, int Row) -{ - public static readonly Position Zero = new(0); - - public Position(int number) - : this(Diagonal: number / 10, Row: number % 10) - { - } - - public bool IsZero => Row == 0 && Diagonal == 0; - public bool IsStart => Row == 1 || Row == Diagonal; - public bool IsEnd => Row == 8 && Diagonal == 15; - - public override string ToString() => $"{Diagonal}{Row}"; - - public static implicit operator Position(int value) => new(value); - - public static Position operator +(Position position, Move move) - => new(Diagonal: position.Diagonal + move.Diagonal, Row: position.Row + move.Row); -} - -internal static class RandomExtensions -{ - internal static Move NextMove(this IRandom random) - => random.NextFloat() switch - { - > 0.6F => Move.Down, - > 0.3F => Move.DownLeft, - _ => Move.Left - }; -} - -internal record struct Move(int Diagonal, int Row) -{ - public static readonly Move Left = new(1, 0); - public static readonly Move DownLeft = new(2, 1); - public static readonly Move Down = new(1, 1); - - public static Move operator *(Move move, int scale) => new(move.Diagonal * scale, move.Row * scale); -} \ No newline at end of file diff --git a/72_Queen/csharp/IOExtensions.cs b/72_Queen/csharp/IOExtensions.cs new file mode 100644 index 00000000..51959967 --- /dev/null +++ b/72_Queen/csharp/IOExtensions.cs @@ -0,0 +1,38 @@ +namespace Queen; + +internal static class IOExtensions +{ + internal static bool ReadYesNo(this IReadWrite io, string prompt) + { + while (true) + { + var answer = io.ReadString(prompt).ToLower(); + if (answer == "yes") { return true; } + if (answer == "no") { return false; } + + io.Write(Streams.YesOrNo); + } + } + + internal static Position ReadPosition( + this IReadWrite io, + string prompt, + Predicate isValid, + Stream error, + bool repeatPrompt = false) + { + while (true) + { + var response = io.ReadNumber(prompt); + var number = (int)response; + var position = new Position(number); + if (number == response && (position.IsZero || isValid(position))) + { + return position; + } + + io.Write(error); + if (!repeatPrompt) { prompt = ""; } + } + } +} diff --git a/72_Queen/csharp/Move.cs b/72_Queen/csharp/Move.cs new file mode 100644 index 00000000..4e18647b --- /dev/null +++ b/72_Queen/csharp/Move.cs @@ -0,0 +1,15 @@ +namespace Queen; + +internal record struct Move(int Diagonal, int Row) +{ + public static readonly Move Left = new(1, 0); + public static readonly Move DownLeft = new(2, 1); + public static readonly Move Down = new(1, 1); + + public bool IsValid => Diagonal > 0 && (IsLeft || IsDown || IsDownLeft); + private bool IsLeft => Row == 0; + private bool IsDown => Row == Diagonal; + private bool IsDownLeft => Row * 2 == Diagonal; + + public static Move operator *(Move move, int scale) => new(move.Diagonal * scale, move.Row * scale); +} \ No newline at end of file diff --git a/72_Queen/csharp/Position.cs b/72_Queen/csharp/Position.cs new file mode 100644 index 00000000..69971163 --- /dev/null +++ b/72_Queen/csharp/Position.cs @@ -0,0 +1,24 @@ +namespace Queen; + +internal record struct Position(int Diagonal, int Row) +{ + public static readonly Position Zero = new(0); + + public Position(int number) + : this(Diagonal: number / 10, Row: number % 10) + { + } + + public bool IsZero => Row == 0 && Diagonal == 0; + public bool IsStart => Row == 1 || Row == Diagonal; + public bool IsEnd => Row == 8 && Diagonal == 15; + + public override string ToString() => $"{Diagonal}{Row}"; + + public static implicit operator Position(int value) => new(value); + + public static Position operator +(Position position, Move move) + => new(Diagonal: position.Diagonal + move.Diagonal, Row: position.Row + move.Row); + public static Move operator -(Position to, Position from) + => new(Diagonal: to.Diagonal - from.Diagonal, Row: to.Row - from.Row); +} diff --git a/72_Queen/csharp/RandomExtensions.cs b/72_Queen/csharp/RandomExtensions.cs new file mode 100644 index 00000000..b4e375fd --- /dev/null +++ b/72_Queen/csharp/RandomExtensions.cs @@ -0,0 +1,12 @@ +namespace Queen; + +internal static class RandomExtensions +{ + internal static Move NextMove(this IRandom random) + => random.NextFloat() switch + { + > 0.6F => Move.Down, + > 0.3F => Move.DownLeft, + _ => Move.Left + }; +} diff --git a/72_Queen/csharp/Resources/IllegalMove.txt b/72_Queen/csharp/Resources/IllegalMove.txt index 3d31638d..ae4a3bbc 100644 --- a/72_Queen/csharp/Resources/IllegalMove.txt +++ b/72_Queen/csharp/Resources/IllegalMove.txt @@ -1,2 +1,2 @@ -Y O U C H E A T . . . Try again +Y O U C H E A T . . . Try again \ No newline at end of file diff --git a/72_Queen/csharp/Resources/Resource.cs b/72_Queen/csharp/Resources/Resource.cs index 9ff43842..e8297eca 100644 --- a/72_Queen/csharp/Resources/Resource.cs +++ b/72_Queen/csharp/Resources/Resource.cs @@ -12,7 +12,6 @@ internal static class Resource public static Stream YesOrNo => GetStream(); public static Stream Board => GetStream(); public static Stream IllegalStart => GetStream(); - public static Stream ComputerMove => GetStream(); public static Stream IllegalMove => GetStream(); public static Stream Forfeit => GetStream(); public static Stream IWin => GetStream(); @@ -28,9 +27,9 @@ internal static class Resource public static string Anyone => GetPrompt(); } - internal static class Formats + internal static class Strings { - public static string Balance => GetString(); + public static string ComputerMove(Position position) => string.Format(GetString(), position); } private static string GetPrompt([CallerMemberName] string? name = null) => GetString($"{name}Prompt"); From 487b7ded98a91ed1a716648210fec038a30183a7 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Tue, 31 Jan 2023 07:40:47 +1100 Subject: [PATCH 014/141] Configure project --- 75_Roulette/csharp/Roulette.csproj | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/75_Roulette/csharp/Roulette.csproj b/75_Roulette/csharp/Roulette.csproj index d3fe4757..23d27b76 100644 --- a/75_Roulette/csharp/Roulette.csproj +++ b/75_Roulette/csharp/Roulette.csproj @@ -1,9 +1,17 @@ Exe - net6.0 + net7.0 10 enable enable + + + + + + + + From 2d562f6051512b767225ea10bdb4a597926e22bf Mon Sep 17 00:00:00 2001 From: gilssonn Date: Sat, 4 Feb 2023 01:14:20 -0500 Subject: [PATCH 015/141] C++ Implementation of Sine Wave --- .../78_Sine_Wave/C++/README.md | 3 +++ .../78_Sine_Wave/C++/sinewave.cpp | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 00_Alternate_Languages/78_Sine_Wave/C++/README.md create mode 100644 00_Alternate_Languages/78_Sine_Wave/C++/sinewave.cpp diff --git a/00_Alternate_Languages/78_Sine_Wave/C++/README.md b/00_Alternate_Languages/78_Sine_Wave/C++/README.md new file mode 100644 index 00000000..b62763ff --- /dev/null +++ b/00_Alternate_Languages/78_Sine_Wave/C++/README.md @@ -0,0 +1,3 @@ +Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) + +Conversion to [C++17](https://en.wikipedia.org/wiki/C%2B%2B17) \ No newline at end of file diff --git a/00_Alternate_Languages/78_Sine_Wave/C++/sinewave.cpp b/00_Alternate_Languages/78_Sine_Wave/C++/sinewave.cpp new file mode 100644 index 00000000..9d274e56 --- /dev/null +++ b/00_Alternate_Languages/78_Sine_Wave/C++/sinewave.cpp @@ -0,0 +1,21 @@ +#include // std::cout, std::endl +#include // std::string(size_t n, char c) +#include // std::sin(double x) + +int main() +{ + std::cout << std::string(30, ' ') << "SINE WAVE" << std::endl; + std::cout << std::string(15, ' ') << "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY" << std::endl; + std::cout << std::string(5, '\n'); + + bool b = true; + + for (double t = 0.0; t <= 40.0; t += 0.25) + { + int a = int(26 + 25 * std::sin(t)); + std::cout << std::string(a, ' ') << (b ? "CREATIVE" : "COMPUTING") << std::endl; + b = !b; + } + + return 0; +} From 3c9c04429d73109999eb17953d5084a6c168cc3c Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Sun, 5 Feb 2023 19:05:23 +1100 Subject: [PATCH 016/141] Add string resources --- 75_Roulette/csharp/Resources/AgainPrompt.txt | 0 75_Roulette/csharp/Resources/BetAlready.txt | 1 + 75_Roulette/csharp/Resources/BetPrompt.txt | 1 + 75_Roulette/csharp/Resources/BrokeHouse.txt | 1 + 75_Roulette/csharp/Resources/Check.txt | 14 +++++ 75_Roulette/csharp/Resources/CheckPrompt.txt | 1 + .../csharp/Resources/HowManyBetsPrompt.txt | 1 + 75_Roulette/csharp/Resources/Instructions.txt | 48 +++++++++++++++ .../csharp/Resources/InstructionsPrompt.txt | 1 + 75_Roulette/csharp/Resources/LastDollar.txt | 1 + 75_Roulette/csharp/Resources/Outcome.txt | 1 + 75_Roulette/csharp/Resources/Resource.cs | 59 +++++++++++++++++++ 75_Roulette/csharp/Resources/Slot.txt | 2 + 75_Roulette/csharp/Resources/Spinning.txt | 3 + 75_Roulette/csharp/Resources/Thanks.txt | 3 + 75_Roulette/csharp/Resources/Title.txt | 7 +++ 75_Roulette/csharp/Resources/Totals.txt | 2 + 17 files changed, 146 insertions(+) create mode 100644 75_Roulette/csharp/Resources/AgainPrompt.txt create mode 100644 75_Roulette/csharp/Resources/BetAlready.txt create mode 100644 75_Roulette/csharp/Resources/BetPrompt.txt create mode 100644 75_Roulette/csharp/Resources/BrokeHouse.txt create mode 100644 75_Roulette/csharp/Resources/Check.txt create mode 100644 75_Roulette/csharp/Resources/CheckPrompt.txt create mode 100644 75_Roulette/csharp/Resources/HowManyBetsPrompt.txt create mode 100644 75_Roulette/csharp/Resources/Instructions.txt create mode 100644 75_Roulette/csharp/Resources/InstructionsPrompt.txt create mode 100644 75_Roulette/csharp/Resources/LastDollar.txt create mode 100644 75_Roulette/csharp/Resources/Outcome.txt create mode 100644 75_Roulette/csharp/Resources/Resource.cs create mode 100644 75_Roulette/csharp/Resources/Slot.txt create mode 100644 75_Roulette/csharp/Resources/Spinning.txt create mode 100644 75_Roulette/csharp/Resources/Thanks.txt create mode 100644 75_Roulette/csharp/Resources/Title.txt create mode 100644 75_Roulette/csharp/Resources/Totals.txt diff --git a/75_Roulette/csharp/Resources/AgainPrompt.txt b/75_Roulette/csharp/Resources/AgainPrompt.txt new file mode 100644 index 00000000..e69de29b diff --git a/75_Roulette/csharp/Resources/BetAlready.txt b/75_Roulette/csharp/Resources/BetAlready.txt new file mode 100644 index 00000000..be86cbea --- /dev/null +++ b/75_Roulette/csharp/Resources/BetAlready.txt @@ -0,0 +1 @@ +You made that bet once already,dum-dum \ No newline at end of file diff --git a/75_Roulette/csharp/Resources/BetPrompt.txt b/75_Roulette/csharp/Resources/BetPrompt.txt new file mode 100644 index 00000000..50039840 --- /dev/null +++ b/75_Roulette/csharp/Resources/BetPrompt.txt @@ -0,0 +1 @@ +Number {0} \ No newline at end of file diff --git a/75_Roulette/csharp/Resources/BrokeHouse.txt b/75_Roulette/csharp/Resources/BrokeHouse.txt new file mode 100644 index 00000000..638dafb4 --- /dev/null +++ b/75_Roulette/csharp/Resources/BrokeHouse.txt @@ -0,0 +1 @@ +You broke the house! diff --git a/75_Roulette/csharp/Resources/Check.txt b/75_Roulette/csharp/Resources/Check.txt new file mode 100644 index 00000000..f2be95cd --- /dev/null +++ b/75_Roulette/csharp/Resources/Check.txt @@ -0,0 +1,14 @@ +------------------------------------------------------------------------Check No. {0} + + {1:mmmm d',' yyyy} + + +Pay to the order of-----{2}-----$ {3} + + + The Memory Bank of New YORK + + The Computer + ----------X----- + +--------------------------------------------------------------Come back soon! diff --git a/75_Roulette/csharp/Resources/CheckPrompt.txt b/75_Roulette/csharp/Resources/CheckPrompt.txt new file mode 100644 index 00000000..16321fe9 --- /dev/null +++ b/75_Roulette/csharp/Resources/CheckPrompt.txt @@ -0,0 +1 @@ +To whom shall I make the check \ No newline at end of file diff --git a/75_Roulette/csharp/Resources/HowManyBetsPrompt.txt b/75_Roulette/csharp/Resources/HowManyBetsPrompt.txt new file mode 100644 index 00000000..0b6abdfa --- /dev/null +++ b/75_Roulette/csharp/Resources/HowManyBetsPrompt.txt @@ -0,0 +1 @@ +How many bets \ No newline at end of file diff --git a/75_Roulette/csharp/Resources/Instructions.txt b/75_Roulette/csharp/Resources/Instructions.txt new file mode 100644 index 00000000..8ced724b --- /dev/null +++ b/75_Roulette/csharp/Resources/Instructions.txt @@ -0,0 +1,48 @@ + +This is the betting layout + (*=RED) + + 1* 2 3* + 4 5* 6 + 7* 8 9* +10 11 12* +--------------- +13 14* 15 +16* 17 18* +19* 20 21* +22 23* 24 +--------------- +25* 26 27* +28 29 30* +31 32* 33 +34* 35 36* +--------------- + 00 0 + +Types of bets + +The numbers 1 to 36 signify a straight bet +on that number. +These pay off 35:1 + +The 2:1 bets are: + 37) 1-12 40) First column + 38) 13-24 41) Second column + 39) 25-36 42) Third column + +The even money bets are: + 43) 1-18 46) Odd + 44) 19-36 47) Red + 45) Even 48) Black + +49)0 and 50)00 pay off 35:1 +Note: 0 and 00 do not count under any + bets except their own. + +When I ask for each bet, type the number +and the amount, separated by a comma. +For example: to bet $500 on Black, type 48,500 +when I ask for a bet. + +The minimum bet is $5, the maximum is $500. + diff --git a/75_Roulette/csharp/Resources/InstructionsPrompt.txt b/75_Roulette/csharp/Resources/InstructionsPrompt.txt new file mode 100644 index 00000000..0d311b60 --- /dev/null +++ b/75_Roulette/csharp/Resources/InstructionsPrompt.txt @@ -0,0 +1 @@ +Do you want instructions \ No newline at end of file diff --git a/75_Roulette/csharp/Resources/LastDollar.txt b/75_Roulette/csharp/Resources/LastDollar.txt new file mode 100644 index 00000000..632516de --- /dev/null +++ b/75_Roulette/csharp/Resources/LastDollar.txt @@ -0,0 +1 @@ +Oops! you just spent your last dollar! diff --git a/75_Roulette/csharp/Resources/Outcome.txt b/75_Roulette/csharp/Resources/Outcome.txt new file mode 100644 index 00000000..7926cb87 --- /dev/null +++ b/75_Roulette/csharp/Resources/Outcome.txt @@ -0,0 +1 @@ +You {0} {1} dollars on bet {2} \ No newline at end of file diff --git a/75_Roulette/csharp/Resources/Resource.cs b/75_Roulette/csharp/Resources/Resource.cs new file mode 100644 index 00000000..aca0aaf5 --- /dev/null +++ b/75_Roulette/csharp/Resources/Resource.cs @@ -0,0 +1,59 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using Games.Common.Randomness; + +namespace Roulette.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream Title => GetStream(); + public static Stream Instructions => GetStream(); + public static Stream BetAlready => GetStream(); + public static Stream Spinning => GetStream(); + public static Stream LastDollar => GetStream(); + public static Stream BrokeHouse => GetStream(); + public static Stream Thanks => GetStream(); + } + + internal static class Strings + { + public static string Black(int number) => Slot(number); + public static string Red(int number) => Slot(number); + private static string Slot(int number, [CallerMemberName] string? colour = null) + => string.Format(GetString(), number, colour); + + public static string Lose(int amount, int bet) => Outcome(amount, bet); + public static string Win(int amount, int bet) => Outcome(amount, bet); + private static string Outcome(int amount, int bet, [CallerMemberName] string? winlose = null) + => string.Format(GetString(), winlose, amount, bet); + + public static string Totals(int me, int you) => string.Format(GetString(), me, you); + + public static string Check(IRandom random, string payee, int amount) + => string.Format(GetString(), random.Next(100), DateTime.Now, payee, amount); + } + + internal static class Prompts + { + public static string Instructions => GetPrompt(); + public static string HowManyBets => GetPrompt(); + public static string Bet(int number) => string.Format(GetPrompt(), number); + public static string Again => GetPrompt(); + public static string Check => GetPrompt(); + } + + private static string GetPrompt([CallerMemberName] string? name = null) => GetString($"{name}Prompt"); + + private static string GetString([CallerMemberName] string? name = null) + { + using var stream = GetStream(name); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + private static Stream GetStream([CallerMemberName] string? name = null) => + Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(Resource).Namespace}.{name}.txt") + ?? throw new Exception($"Could not find embedded resource stream '{name}'."); +} \ No newline at end of file diff --git a/75_Roulette/csharp/Resources/Slot.txt b/75_Roulette/csharp/Resources/Slot.txt new file mode 100644 index 00000000..d45e103b --- /dev/null +++ b/75_Roulette/csharp/Resources/Slot.txt @@ -0,0 +1,2 @@ + {0} {1} + \ No newline at end of file diff --git a/75_Roulette/csharp/Resources/Spinning.txt b/75_Roulette/csharp/Resources/Spinning.txt new file mode 100644 index 00000000..13514a0a --- /dev/null +++ b/75_Roulette/csharp/Resources/Spinning.txt @@ -0,0 +1,3 @@ +SPINNING + + diff --git a/75_Roulette/csharp/Resources/Thanks.txt b/75_Roulette/csharp/Resources/Thanks.txt new file mode 100644 index 00000000..0b835237 --- /dev/null +++ b/75_Roulette/csharp/Resources/Thanks.txt @@ -0,0 +1,3 @@ +Thanks for you money. +I'll use it to buy a solid gold roulette WHEEL + diff --git a/75_Roulette/csharp/Resources/Title.txt b/75_Roulette/csharp/Resources/Title.txt new file mode 100644 index 00000000..0d53f1a8 --- /dev/null +++ b/75_Roulette/csharp/Resources/Title.txt @@ -0,0 +1,7 @@ + Roulette + Creative Computing Morristown, New Jersey + + + +Welcome to the roulette table + diff --git a/75_Roulette/csharp/Resources/Totals.txt b/75_Roulette/csharp/Resources/Totals.txt new file mode 100644 index 00000000..26f35724 --- /dev/null +++ b/75_Roulette/csharp/Resources/Totals.txt @@ -0,0 +1,2 @@ +Totals Me You + {0,-14}{1} From 9a1e8e88752b26c3b3e3c7c7d1adccd9806b4b1b Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Sun, 5 Feb 2023 21:28:51 +1100 Subject: [PATCH 017/141] Add game start --- 75_Roulette/csharp/Game.cs | 18 ++++++++++++++++++ 75_Roulette/csharp/Program.cs | 6 ++++++ 2 files changed, 24 insertions(+) create mode 100644 75_Roulette/csharp/Game.cs create mode 100644 75_Roulette/csharp/Program.cs diff --git a/75_Roulette/csharp/Game.cs b/75_Roulette/csharp/Game.cs new file mode 100644 index 00000000..efb4b966 --- /dev/null +++ b/75_Roulette/csharp/Game.cs @@ -0,0 +1,18 @@ +namespace Roulette; + +internal class Game +{ + private readonly IReadWrite _io; + private readonly IRandom _random; + + public Game(IReadWrite io, IRandom random) + { + _io = io; + _random = random; + } + + public void Play() + { + + } +} \ No newline at end of file diff --git a/75_Roulette/csharp/Program.cs b/75_Roulette/csharp/Program.cs new file mode 100644 index 00000000..4be74962 --- /dev/null +++ b/75_Roulette/csharp/Program.cs @@ -0,0 +1,6 @@ +global using Games.Common.IO; +global using Games.Common.Randomness; +global using static Roulette.Resources.Resource; +using Roulette; + +new Game(new ConsoleIO(), new RandomNumberGenerator()).Play(); From 25dba634c5f80d33d4d0af458ce2e288696a73b9 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Sun, 5 Feb 2023 22:47:02 +1100 Subject: [PATCH 018/141] Add game loop and objects --- 75_Roulette/csharp/Game.cs | 140 ++++++++++++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 1 deletion(-) diff --git a/75_Roulette/csharp/Game.cs b/75_Roulette/csharp/Game.cs index efb4b966..c9570ca1 100644 --- a/75_Roulette/csharp/Game.cs +++ b/75_Roulette/csharp/Game.cs @@ -1,18 +1,156 @@ +using System.Collections.Immutable; + namespace Roulette; internal class Game { private readonly IReadWrite _io; private readonly IRandom _random; + private readonly Table _table; public Game(IReadWrite io, IRandom random) { _io = io; _random = random; + _table = new Table(io, random); } public void Play() { - + _io.Write(Streams.Title); + if (!_io.ReadString(Prompts.Instructions).ToLowerInvariant().StartsWith('n')) + { + _io.Write(Streams.Instructions); + } + + while (_table.Play()); + + if (_table.Balance > 0) + { + var name = _io.ReadString(Prompts.Check); + _io.Write(Strings.Check(_random, name, _table.Balance)); + } + else + { + _io.Write(Streams.Thanks); + } } +} + +internal class Wheel +{ + private static readonly ImmutableArray _slots = ImmutableArray.Create( + new Slot(Strings.Red(1)), + new Slot(Strings.Black(2)), + new Slot(Strings.Red(3)), + new Slot(Strings.Black(4)), + new Slot(Strings.Red(5)), + new Slot(Strings.Black(6)), + new Slot(Strings.Red(7)), + new Slot(Strings.Black(8)), + new Slot(Strings.Red(9)), + new Slot(Strings.Black(10)), + new Slot(Strings.Black(11)), + new Slot(Strings.Red(12)), + new Slot(Strings.Black(13)), + new Slot(Strings.Red(14)), + new Slot(Strings.Black(15)), + new Slot(Strings.Red(16)), + new Slot(Strings.Black(17)), + new Slot(Strings.Red(18)), + new Slot(Strings.Red(19)), + new Slot(Strings.Black(20)), + new Slot(Strings.Red(21)), + new Slot(Strings.Black(22)), + new Slot(Strings.Red(23)), + new Slot(Strings.Black(24)), + new Slot(Strings.Red(25)), + new Slot(Strings.Black(26)), + new Slot(Strings.Red(27)), + new Slot(Strings.Black(28)), + new Slot(Strings.Black(29)), + new Slot(Strings.Red(30)), + new Slot(Strings.Black(31)), + new Slot(Strings.Red(32)), + new Slot(Strings.Black(33)), + new Slot(Strings.Red(34)), + new Slot(Strings.Black(35)), + new Slot(Strings.Red(36)), + new Slot("0"), + new Slot("00")); + + private readonly IRandom _random; + + public Wheel(IRandom random) => _random = random; + + public Slot Spin() => _slots[_random.Next(_slots.Length)]; +} + +internal record struct Slot(string Name); + +internal record struct Bet(int Number, int Amount) +{ + public Bet(int number) : this(number, 0) { } + + public bool Equals(Bet? other) => Number == other?.Number; +} + +public class Table +{ + private readonly IReadWrite _io; + private readonly Wheel _wheel; + + private int _houseBalance = 100_000; + private int _playerBalance = 1_000; + + public Table(IReadWrite io, IRandom random) + { + _io = io; + _wheel = new(random); + } + + public int Balance => _playerBalance; + + public bool Play() + { + var betCount = _io.ReadBetCount(); + var bets = new HashSet(); + for (int i = 0; i < betCount; i++) + { + while (!bets.Add(_io.ReadBet(i))) + { + _io.Write(Streams.BetAlready); + } + } + + return _io.ReadString(Prompts.Again).ToLowerInvariant().StartsWith('y'); + } +} + +internal static class IOExtensions +{ + internal static int ReadBetCount(this IReadWrite io) + { + while (true) + { + var betCount = io.ReadNumber(Prompts.HowManyBets); + if (betCount.IsValidInt(1)) { return (int)betCount; } + } + } + + internal static Bet ReadBet(this IReadWrite io, int number) + { + while (true) + { + var (bet, amount) = io.Read2Numbers(Prompts.Bet(number)); + + if (bet.IsValidInt(1, 50) && amount.IsValidInt(5, 500)) + { + return new((int)bet, (int)amount); + } + } + } + + internal static bool IsValidInt(this float value, int minValue, int maxValue = int.MaxValue) + => value == (int)value && value >= minValue && value <= maxValue; } \ No newline at end of file From 2d9d890269b79e6177c3370fd2d431a0cf520cea Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Wed, 15 Feb 2023 17:49:22 +1100 Subject: [PATCH 019/141] Finish game logic --- 75_Roulette/csharp/Game.cs | 243 ++++++++++++++----- 75_Roulette/csharp/Resources/AgainPrompt.txt | 1 + 75_Roulette/csharp/Resources/Check.txt | 4 +- 75_Roulette/csharp/Resources/Outcome.txt | 2 +- 75_Roulette/csharp/Resources/Resource.cs | 8 +- 75_Roulette/csharp/Resources/Slot.txt | 2 +- 75_Roulette/csharp/Resources/Spinning.txt | 2 +- 75_Roulette/csharp/Resources/Totals.txt | 1 + 8 files changed, 193 insertions(+), 70 deletions(-) diff --git a/75_Roulette/csharp/Game.cs b/75_Roulette/csharp/Game.cs index c9570ca1..4543737f 100644 --- a/75_Roulette/csharp/Game.cs +++ b/75_Roulette/csharp/Game.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; namespace Roulette; @@ -7,12 +8,14 @@ internal class Game private readonly IReadWrite _io; private readonly IRandom _random; private readonly Table _table; + private readonly House _house; public Game(IReadWrite io, IRandom random) { _io = io; _random = random; - _table = new Table(io, random); + _house = new(); + _table = new(_house, io, random); } public void Play() @@ -25,10 +28,9 @@ internal class Game while (_table.Play()); - if (_table.Balance > 0) + if (!_house.PlayerIsBroke) { - var name = _io.ReadString(Prompts.Check); - _io.Write(Strings.Check(_random, name, _table.Balance)); + _house.CutCheck(_io, _random); } else { @@ -40,44 +42,44 @@ internal class Game internal class Wheel { private static readonly ImmutableArray _slots = ImmutableArray.Create( - new Slot(Strings.Red(1)), - new Slot(Strings.Black(2)), - new Slot(Strings.Red(3)), - new Slot(Strings.Black(4)), - new Slot(Strings.Red(5)), - new Slot(Strings.Black(6)), - new Slot(Strings.Red(7)), - new Slot(Strings.Black(8)), - new Slot(Strings.Red(9)), - new Slot(Strings.Black(10)), - new Slot(Strings.Black(11)), - new Slot(Strings.Red(12)), - new Slot(Strings.Black(13)), - new Slot(Strings.Red(14)), - new Slot(Strings.Black(15)), - new Slot(Strings.Red(16)), - new Slot(Strings.Black(17)), - new Slot(Strings.Red(18)), - new Slot(Strings.Red(19)), - new Slot(Strings.Black(20)), - new Slot(Strings.Red(21)), - new Slot(Strings.Black(22)), - new Slot(Strings.Red(23)), - new Slot(Strings.Black(24)), - new Slot(Strings.Red(25)), - new Slot(Strings.Black(26)), - new Slot(Strings.Red(27)), - new Slot(Strings.Black(28)), - new Slot(Strings.Black(29)), - new Slot(Strings.Red(30)), - new Slot(Strings.Black(31)), - new Slot(Strings.Red(32)), - new Slot(Strings.Black(33)), - new Slot(Strings.Red(34)), - new Slot(Strings.Black(35)), - new Slot(Strings.Red(36)), - new Slot("0"), - new Slot("00")); + new Slot(Strings.Red(1), 1, 37, 40, 43, 46, 47), + new Slot(Strings.Black(2), 2, 37, 41, 43, 45, 48), + new Slot(Strings.Red(3), 3, 37, 42, 43, 46, 47), + new Slot(Strings.Black(4), 4, 37, 40, 43, 45, 48), + new Slot(Strings.Red(5), 5, 37, 41, 43, 46, 47), + new Slot(Strings.Black(6), 6, 37, 42, 43, 45, 48), + new Slot(Strings.Red(7), 7, 37, 40, 43, 46, 47), + new Slot(Strings.Black(8), 8, 37, 41, 43, 45, 48), + new Slot(Strings.Red(9), 9, 37, 42, 43, 46, 47), + new Slot(Strings.Black(10), 10, 37, 40, 43, 45, 48), + new Slot(Strings.Black(11), 11, 37, 41, 43, 46, 48), + new Slot(Strings.Red(12), 12, 37, 42, 43, 45, 47), + new Slot(Strings.Black(13), 13, 38, 40, 43, 46, 48), + new Slot(Strings.Red(14), 14, 38, 41, 43, 45, 47), + new Slot(Strings.Black(15), 15, 38, 42, 43, 46, 48), + new Slot(Strings.Red(16), 16, 38, 40, 43, 45, 47), + new Slot(Strings.Black(17), 17, 38, 41, 43, 46, 48), + new Slot(Strings.Red(18), 18, 38, 42, 43, 45, 47), + new Slot(Strings.Red(19), 19, 38, 40, 44, 46, 47), + new Slot(Strings.Black(20), 20, 38, 41, 44, 45, 48), + new Slot(Strings.Red(21), 21, 38, 42, 44, 46, 47), + new Slot(Strings.Black(22), 22, 38, 40, 44, 45, 48), + new Slot(Strings.Red(23), 23, 38, 41, 44, 46, 47), + new Slot(Strings.Black(24), 24, 38, 42, 44, 45, 48), + new Slot(Strings.Red(25), 25, 39, 40, 44, 46, 47), + new Slot(Strings.Black(26), 26, 39, 41, 44, 45, 48), + new Slot(Strings.Red(27), 27, 39, 42, 44, 46, 47), + new Slot(Strings.Black(28), 28, 39, 40, 44, 45, 48), + new Slot(Strings.Black(29), 29, 39, 41, 44, 46, 48), + new Slot(Strings.Red(30), 30, 39, 42, 44, 45, 47), + new Slot(Strings.Black(31), 31, 39, 40, 44, 46, 48), + new Slot(Strings.Red(32), 32, 39, 41, 44, 45, 47), + new Slot(Strings.Black(33), 33, 39, 42, 44, 46, 48), + new Slot(Strings.Red(34), 34, 39, 40, 44, 45, 47), + new Slot(Strings.Black(35), 35, 39, 41, 44, 46, 48), + new Slot(Strings.Red(36), 36, 39, 42, 44, 45, 47), + new Slot("0", 49), + new Slot("00", 50)); private readonly IRandom _random; @@ -86,44 +88,156 @@ internal class Wheel public Slot Spin() => _slots[_random.Next(_slots.Length)]; } -internal record struct Slot(string Name); - -internal record struct Bet(int Number, int Amount) +internal class Slot { - public Bet(int number) : this(number, 0) { } + private readonly ImmutableHashSet _coveringBets; - public bool Equals(Bet? other) => Number == other?.Number; + public Slot (string name, params BetType[] coveringBets) + { + Name = name; + _coveringBets = coveringBets.ToImmutableHashSet(); + } + + public string Name { get; } + + public bool IsCoveredBy(Bet bet) => _coveringBets.Contains(bet.Type); +} + +internal record struct BetType(int Value) +{ + public static implicit operator BetType(int value) => new(value); + + public int Payout => Value switch + { + <= 36 or >= 49 => 35, + <= 42 => 2, + <= 48 => 1 + }; +} + +internal record struct Bet(BetType Type, int Number, int Wager) +{ + public int Payout => Wager * Type.Payout; } public class Table { private readonly IReadWrite _io; private readonly Wheel _wheel; + private readonly House _house; - private int _houseBalance = 100_000; - private int _playerBalance = 1_000; - - public Table(IReadWrite io, IRandom random) + public Table(House house, IReadWrite io, IRandom random) { + _house = house; _io = io; _wheel = new(random); } - public int Balance => _playerBalance; - public bool Play() { - var betCount = _io.ReadBetCount(); - var bets = new HashSet(); - for (int i = 0; i < betCount; i++) + var bets = AcceptBets(); + var slot = SpinWheel(); + SettleBets(bets, slot); + + _io.Write(_house.Totals); + + if (_house.PlayerIsBroke) { - while (!bets.Add(_io.ReadBet(i))) + _io.Write(Streams.LastDollar); + _io.Write(Streams.Thanks); + return false; + } + + if (_house.HouseIsBroke) + { + _io.Write(Streams.BrokeHouse); + return false; + } + + return _io.ReadString(Prompts.Again).ToLowerInvariant().StartsWith('y'); + } + + private Slot SpinWheel() + { + _io.Write(Streams.Spinning); + var slot = _wheel.Spin(); + _io.Write(slot.Name); + return slot; + } + + private IReadOnlyList AcceptBets() + { + var betCount = _io.ReadBetCount(); + var betTypes = new HashSet(); + var bets = new List(); + for (int i = 1; i <= betCount; i++) + { + while (!TryAdd(_io.ReadBet(i))) { _io.Write(Streams.BetAlready); } } - - return _io.ReadString(Prompts.Again).ToLowerInvariant().StartsWith('y'); + + return bets.AsReadOnly(); + + bool TryAdd(Bet bet) + { + if (betTypes.Add(bet.Type)) + { + bets.Add(bet); + return true; + } + + return false; + } + } + + private void SettleBets(IReadOnlyList bets, Slot slot) + { + foreach (var bet in bets) + { + _io.Write(slot.IsCoveredBy(bet) ? _house.Pay(bet) : _house.Take(bet)); + } + } +} + +public class House +{ + private const int _initialHouse = 100_000; + private const int _initialPlayer = 1_000; + + private int _balance = _initialHouse; + private int _player = _initialPlayer; + + public string Totals => Strings.Totals(_balance, _player); + public bool PlayerIsBroke => _player <= 0; + public bool HouseIsBroke => _balance <= 0; + + internal string Pay(Bet bet) + { + _balance -= bet.Payout; + _player += bet.Payout; + + if (_balance <= 0) + { + _player = _initialHouse + _initialPlayer; + } + + return Strings.Win(bet); + } + + internal string Take(Bet bet) + { + _balance += bet.Wager; + _player -= bet.Wager; + + return Strings.Lose(bet); + } + + public void CutCheck(IReadWrite io, IRandom random) + { + var name = io.ReadString(Prompts.Check); + io.Write(Strings.Check(random, name, _player)); } } @@ -142,11 +256,16 @@ internal static class IOExtensions { while (true) { - var (bet, amount) = io.Read2Numbers(Prompts.Bet(number)); + var (type, amount) = io.Read2Numbers(Prompts.Bet(number)); - if (bet.IsValidInt(1, 50) && amount.IsValidInt(5, 500)) + if (type.IsValidInt(1, 50) && amount.IsValidInt(5, 500)) { - return new((int)bet, (int)amount); + return new() + { + Type = (int)type, + Number = number, + Wager = (int)amount + }; } } } diff --git a/75_Roulette/csharp/Resources/AgainPrompt.txt b/75_Roulette/csharp/Resources/AgainPrompt.txt index e69de29b..bd1d18b6 100644 --- a/75_Roulette/csharp/Resources/AgainPrompt.txt +++ b/75_Roulette/csharp/Resources/AgainPrompt.txt @@ -0,0 +1 @@ +Again \ No newline at end of file diff --git a/75_Roulette/csharp/Resources/Check.txt b/75_Roulette/csharp/Resources/Check.txt index f2be95cd..ffdd640a 100644 --- a/75_Roulette/csharp/Resources/Check.txt +++ b/75_Roulette/csharp/Resources/Check.txt @@ -1,6 +1,7 @@ + ------------------------------------------------------------------------Check No. {0} - {1:mmmm d',' yyyy} + {1:MMMM d',' yyyy} Pay to the order of-----{2}-----$ {3} @@ -12,3 +13,4 @@ Pay to the order of-----{2}-----$ {3} ----------X----- --------------------------------------------------------------Come back soon! + diff --git a/75_Roulette/csharp/Resources/Outcome.txt b/75_Roulette/csharp/Resources/Outcome.txt index 7926cb87..30e227f7 100644 --- a/75_Roulette/csharp/Resources/Outcome.txt +++ b/75_Roulette/csharp/Resources/Outcome.txt @@ -1 +1 @@ -You {0} {1} dollars on bet {2} \ No newline at end of file +You {0} {1} dollars on bet {2} diff --git a/75_Roulette/csharp/Resources/Resource.cs b/75_Roulette/csharp/Resources/Resource.cs index aca0aaf5..dd9c8672 100644 --- a/75_Roulette/csharp/Resources/Resource.cs +++ b/75_Roulette/csharp/Resources/Resource.cs @@ -24,10 +24,10 @@ internal static class Resource private static string Slot(int number, [CallerMemberName] string? colour = null) => string.Format(GetString(), number, colour); - public static string Lose(int amount, int bet) => Outcome(amount, bet); - public static string Win(int amount, int bet) => Outcome(amount, bet); - private static string Outcome(int amount, int bet, [CallerMemberName] string? winlose = null) - => string.Format(GetString(), winlose, amount, bet); + public static string Lose(Bet bet) => Outcome(bet.Wager, bet.Number); + public static string Win(Bet bet) => Outcome(bet.Payout, bet.Number); + private static string Outcome(int amount, int number, [CallerMemberName] string? winlose = null) + => string.Format(GetString(), winlose, amount, number); public static string Totals(int me, int you) => string.Format(GetString(), me, you); diff --git a/75_Roulette/csharp/Resources/Slot.txt b/75_Roulette/csharp/Resources/Slot.txt index d45e103b..de02695d 100644 --- a/75_Roulette/csharp/Resources/Slot.txt +++ b/75_Roulette/csharp/Resources/Slot.txt @@ -1,2 +1,2 @@ {0} {1} - \ No newline at end of file + diff --git a/75_Roulette/csharp/Resources/Spinning.txt b/75_Roulette/csharp/Resources/Spinning.txt index 13514a0a..0d87fe39 100644 --- a/75_Roulette/csharp/Resources/Spinning.txt +++ b/75_Roulette/csharp/Resources/Spinning.txt @@ -1,3 +1,3 @@ -SPINNING +Spinning diff --git a/75_Roulette/csharp/Resources/Totals.txt b/75_Roulette/csharp/Resources/Totals.txt index 26f35724..4cecae3f 100644 --- a/75_Roulette/csharp/Resources/Totals.txt +++ b/75_Roulette/csharp/Resources/Totals.txt @@ -1,2 +1,3 @@ + Totals Me You {0,-14}{1} From 699412adb81c8524f435f6b4b3042a496847c60f Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Thu, 16 Feb 2023 12:22:59 +1100 Subject: [PATCH 020/141] Reorganise code --- 75_Roulette/csharp/Bet.cs | 6 + 75_Roulette/csharp/BetType.cs | 13 ++ 75_Roulette/csharp/Croupier.cs | 41 +++++ 75_Roulette/csharp/Game.cs | 238 +---------------------------- 75_Roulette/csharp/IOExtensions.cs | 34 +++++ 75_Roulette/csharp/Slot.cs | 18 +++ 75_Roulette/csharp/Table.cs | 82 ++++++++++ 75_Roulette/csharp/Wheel.cs | 52 +++++++ 8 files changed, 247 insertions(+), 237 deletions(-) create mode 100644 75_Roulette/csharp/Bet.cs create mode 100644 75_Roulette/csharp/BetType.cs create mode 100644 75_Roulette/csharp/Croupier.cs create mode 100644 75_Roulette/csharp/IOExtensions.cs create mode 100644 75_Roulette/csharp/Slot.cs create mode 100644 75_Roulette/csharp/Table.cs create mode 100644 75_Roulette/csharp/Wheel.cs diff --git a/75_Roulette/csharp/Bet.cs b/75_Roulette/csharp/Bet.cs new file mode 100644 index 00000000..da0e31b7 --- /dev/null +++ b/75_Roulette/csharp/Bet.cs @@ -0,0 +1,6 @@ +namespace Roulette; + +internal record struct Bet(BetType Type, int Number, int Wager) +{ + public int Payout => Wager * Type.Payout; +} diff --git a/75_Roulette/csharp/BetType.cs b/75_Roulette/csharp/BetType.cs new file mode 100644 index 00000000..79b3b67c --- /dev/null +++ b/75_Roulette/csharp/BetType.cs @@ -0,0 +1,13 @@ +namespace Roulette; + +internal record struct BetType(int Value) +{ + public static implicit operator BetType(int value) => new(value); + + public int Payout => Value switch + { + <= 36 or >= 49 => 35, + <= 42 => 2, + <= 48 => 1 + }; +} diff --git a/75_Roulette/csharp/Croupier.cs b/75_Roulette/csharp/Croupier.cs new file mode 100644 index 00000000..32f76c33 --- /dev/null +++ b/75_Roulette/csharp/Croupier.cs @@ -0,0 +1,41 @@ +namespace Roulette; + +internal class Croupier +{ + private const int _initialHouse = 100_000; + private const int _initialPlayer = 1_000; + + private int _house = _initialHouse; + private int _player = _initialPlayer; + + public string Totals => Strings.Totals(_house, _player); + public bool PlayerIsBroke => _player <= 0; + public bool HouseIsBroke => _house <= 0; + + internal string Pay(Bet bet) + { + _house -= bet.Payout; + _player += bet.Payout; + + if (_house <= 0) + { + _player = _initialHouse + _initialPlayer; + } + + return Strings.Win(bet); + } + + internal string Take(Bet bet) + { + _house += bet.Wager; + _player -= bet.Wager; + + return Strings.Lose(bet); + } + + public void CutCheck(IReadWrite io, IRandom random) + { + var name = io.ReadString(Prompts.Check); + io.Write(Strings.Check(random, name, _player)); + } +} diff --git a/75_Roulette/csharp/Game.cs b/75_Roulette/csharp/Game.cs index 4543737f..aa077042 100644 --- a/75_Roulette/csharp/Game.cs +++ b/75_Roulette/csharp/Game.cs @@ -1,4 +1,3 @@ -using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; namespace Roulette; @@ -8,7 +7,7 @@ internal class Game private readonly IReadWrite _io; private readonly IRandom _random; private readonly Table _table; - private readonly House _house; + private readonly Croupier _house; public Game(IReadWrite io, IRandom random) { @@ -38,238 +37,3 @@ internal class Game } } } - -internal class Wheel -{ - private static readonly ImmutableArray _slots = ImmutableArray.Create( - new Slot(Strings.Red(1), 1, 37, 40, 43, 46, 47), - new Slot(Strings.Black(2), 2, 37, 41, 43, 45, 48), - new Slot(Strings.Red(3), 3, 37, 42, 43, 46, 47), - new Slot(Strings.Black(4), 4, 37, 40, 43, 45, 48), - new Slot(Strings.Red(5), 5, 37, 41, 43, 46, 47), - new Slot(Strings.Black(6), 6, 37, 42, 43, 45, 48), - new Slot(Strings.Red(7), 7, 37, 40, 43, 46, 47), - new Slot(Strings.Black(8), 8, 37, 41, 43, 45, 48), - new Slot(Strings.Red(9), 9, 37, 42, 43, 46, 47), - new Slot(Strings.Black(10), 10, 37, 40, 43, 45, 48), - new Slot(Strings.Black(11), 11, 37, 41, 43, 46, 48), - new Slot(Strings.Red(12), 12, 37, 42, 43, 45, 47), - new Slot(Strings.Black(13), 13, 38, 40, 43, 46, 48), - new Slot(Strings.Red(14), 14, 38, 41, 43, 45, 47), - new Slot(Strings.Black(15), 15, 38, 42, 43, 46, 48), - new Slot(Strings.Red(16), 16, 38, 40, 43, 45, 47), - new Slot(Strings.Black(17), 17, 38, 41, 43, 46, 48), - new Slot(Strings.Red(18), 18, 38, 42, 43, 45, 47), - new Slot(Strings.Red(19), 19, 38, 40, 44, 46, 47), - new Slot(Strings.Black(20), 20, 38, 41, 44, 45, 48), - new Slot(Strings.Red(21), 21, 38, 42, 44, 46, 47), - new Slot(Strings.Black(22), 22, 38, 40, 44, 45, 48), - new Slot(Strings.Red(23), 23, 38, 41, 44, 46, 47), - new Slot(Strings.Black(24), 24, 38, 42, 44, 45, 48), - new Slot(Strings.Red(25), 25, 39, 40, 44, 46, 47), - new Slot(Strings.Black(26), 26, 39, 41, 44, 45, 48), - new Slot(Strings.Red(27), 27, 39, 42, 44, 46, 47), - new Slot(Strings.Black(28), 28, 39, 40, 44, 45, 48), - new Slot(Strings.Black(29), 29, 39, 41, 44, 46, 48), - new Slot(Strings.Red(30), 30, 39, 42, 44, 45, 47), - new Slot(Strings.Black(31), 31, 39, 40, 44, 46, 48), - new Slot(Strings.Red(32), 32, 39, 41, 44, 45, 47), - new Slot(Strings.Black(33), 33, 39, 42, 44, 46, 48), - new Slot(Strings.Red(34), 34, 39, 40, 44, 45, 47), - new Slot(Strings.Black(35), 35, 39, 41, 44, 46, 48), - new Slot(Strings.Red(36), 36, 39, 42, 44, 45, 47), - new Slot("0", 49), - new Slot("00", 50)); - - private readonly IRandom _random; - - public Wheel(IRandom random) => _random = random; - - public Slot Spin() => _slots[_random.Next(_slots.Length)]; -} - -internal class Slot -{ - private readonly ImmutableHashSet _coveringBets; - - public Slot (string name, params BetType[] coveringBets) - { - Name = name; - _coveringBets = coveringBets.ToImmutableHashSet(); - } - - public string Name { get; } - - public bool IsCoveredBy(Bet bet) => _coveringBets.Contains(bet.Type); -} - -internal record struct BetType(int Value) -{ - public static implicit operator BetType(int value) => new(value); - - public int Payout => Value switch - { - <= 36 or >= 49 => 35, - <= 42 => 2, - <= 48 => 1 - }; -} - -internal record struct Bet(BetType Type, int Number, int Wager) -{ - public int Payout => Wager * Type.Payout; -} - -public class Table -{ - private readonly IReadWrite _io; - private readonly Wheel _wheel; - private readonly House _house; - - public Table(House house, IReadWrite io, IRandom random) - { - _house = house; - _io = io; - _wheel = new(random); - } - - public bool Play() - { - var bets = AcceptBets(); - var slot = SpinWheel(); - SettleBets(bets, slot); - - _io.Write(_house.Totals); - - if (_house.PlayerIsBroke) - { - _io.Write(Streams.LastDollar); - _io.Write(Streams.Thanks); - return false; - } - - if (_house.HouseIsBroke) - { - _io.Write(Streams.BrokeHouse); - return false; - } - - return _io.ReadString(Prompts.Again).ToLowerInvariant().StartsWith('y'); - } - - private Slot SpinWheel() - { - _io.Write(Streams.Spinning); - var slot = _wheel.Spin(); - _io.Write(slot.Name); - return slot; - } - - private IReadOnlyList AcceptBets() - { - var betCount = _io.ReadBetCount(); - var betTypes = new HashSet(); - var bets = new List(); - for (int i = 1; i <= betCount; i++) - { - while (!TryAdd(_io.ReadBet(i))) - { - _io.Write(Streams.BetAlready); - } - } - - return bets.AsReadOnly(); - - bool TryAdd(Bet bet) - { - if (betTypes.Add(bet.Type)) - { - bets.Add(bet); - return true; - } - - return false; - } - } - - private void SettleBets(IReadOnlyList bets, Slot slot) - { - foreach (var bet in bets) - { - _io.Write(slot.IsCoveredBy(bet) ? _house.Pay(bet) : _house.Take(bet)); - } - } -} - -public class House -{ - private const int _initialHouse = 100_000; - private const int _initialPlayer = 1_000; - - private int _balance = _initialHouse; - private int _player = _initialPlayer; - - public string Totals => Strings.Totals(_balance, _player); - public bool PlayerIsBroke => _player <= 0; - public bool HouseIsBroke => _balance <= 0; - - internal string Pay(Bet bet) - { - _balance -= bet.Payout; - _player += bet.Payout; - - if (_balance <= 0) - { - _player = _initialHouse + _initialPlayer; - } - - return Strings.Win(bet); - } - - internal string Take(Bet bet) - { - _balance += bet.Wager; - _player -= bet.Wager; - - return Strings.Lose(bet); - } - - public void CutCheck(IReadWrite io, IRandom random) - { - var name = io.ReadString(Prompts.Check); - io.Write(Strings.Check(random, name, _player)); - } -} - -internal static class IOExtensions -{ - internal static int ReadBetCount(this IReadWrite io) - { - while (true) - { - var betCount = io.ReadNumber(Prompts.HowManyBets); - if (betCount.IsValidInt(1)) { return (int)betCount; } - } - } - - internal static Bet ReadBet(this IReadWrite io, int number) - { - while (true) - { - var (type, amount) = io.Read2Numbers(Prompts.Bet(number)); - - if (type.IsValidInt(1, 50) && amount.IsValidInt(5, 500)) - { - return new() - { - Type = (int)type, - Number = number, - Wager = (int)amount - }; - } - } - } - - internal static bool IsValidInt(this float value, int minValue, int maxValue = int.MaxValue) - => value == (int)value && value >= minValue && value <= maxValue; -} \ No newline at end of file diff --git a/75_Roulette/csharp/IOExtensions.cs b/75_Roulette/csharp/IOExtensions.cs new file mode 100644 index 00000000..49326bef --- /dev/null +++ b/75_Roulette/csharp/IOExtensions.cs @@ -0,0 +1,34 @@ +namespace Roulette; + +internal static class IOExtensions +{ + internal static int ReadBetCount(this IReadWrite io) + { + while (true) + { + var betCount = io.ReadNumber(Prompts.HowManyBets); + if (betCount.IsValidInt(1)) { return (int)betCount; } + } + } + + internal static Bet ReadBet(this IReadWrite io, int number) + { + while (true) + { + var (type, amount) = io.Read2Numbers(Prompts.Bet(number)); + + if (type.IsValidInt(1, 50) && amount.IsValidInt(5, 500)) + { + return new() + { + Type = (int)type, + Number = number, + Wager = (int)amount + }; + } + } + } + + internal static bool IsValidInt(this float value, int minValue, int maxValue = int.MaxValue) + => value == (int)value && value >= minValue && value <= maxValue; +} \ No newline at end of file diff --git a/75_Roulette/csharp/Slot.cs b/75_Roulette/csharp/Slot.cs new file mode 100644 index 00000000..df30cea1 --- /dev/null +++ b/75_Roulette/csharp/Slot.cs @@ -0,0 +1,18 @@ +using System.Collections.Immutable; + +namespace Roulette; + +internal class Slot +{ + private readonly ImmutableHashSet _coveringBets; + + public Slot (string name, params BetType[] coveringBets) + { + Name = name; + _coveringBets = coveringBets.ToImmutableHashSet(); + } + + public string Name { get; } + + public bool IsCoveredBy(Bet bet) => _coveringBets.Contains(bet.Type); +} diff --git a/75_Roulette/csharp/Table.cs b/75_Roulette/csharp/Table.cs new file mode 100644 index 00000000..fbc6341e --- /dev/null +++ b/75_Roulette/csharp/Table.cs @@ -0,0 +1,82 @@ +namespace Roulette; + +internal class Table +{ + private readonly IReadWrite _io; + private readonly Wheel _wheel; + private readonly Croupier _house; + + public Table(Croupier house, IReadWrite io, IRandom random) + { + _house = house; + _io = io; + _wheel = new(random); + } + + public bool Play() + { + var bets = AcceptBets(); + var slot = SpinWheel(); + SettleBets(bets, slot); + + _io.Write(_house.Totals); + + if (_house.PlayerIsBroke) + { + _io.Write(Streams.LastDollar); + _io.Write(Streams.Thanks); + return false; + } + + if (_house.HouseIsBroke) + { + _io.Write(Streams.BrokeHouse); + return false; + } + + return _io.ReadString(Prompts.Again).ToLowerInvariant().StartsWith('y'); + } + + private Slot SpinWheel() + { + _io.Write(Streams.Spinning); + var slot = _wheel.Spin(); + _io.Write(slot.Name); + return slot; + } + + private IReadOnlyList AcceptBets() + { + var betCount = _io.ReadBetCount(); + var betTypes = new HashSet(); + var bets = new List(); + for (int i = 1; i <= betCount; i++) + { + while (!TryAdd(_io.ReadBet(i))) + { + _io.Write(Streams.BetAlready); + } + } + + return bets.AsReadOnly(); + + bool TryAdd(Bet bet) + { + if (betTypes.Add(bet.Type)) + { + bets.Add(bet); + return true; + } + + return false; + } + } + + private void SettleBets(IReadOnlyList bets, Slot slot) + { + foreach (var bet in bets) + { + _io.Write(slot.IsCoveredBy(bet) ? _house.Pay(bet) : _house.Take(bet)); + } + } +} diff --git a/75_Roulette/csharp/Wheel.cs b/75_Roulette/csharp/Wheel.cs new file mode 100644 index 00000000..dfcecd29 --- /dev/null +++ b/75_Roulette/csharp/Wheel.cs @@ -0,0 +1,52 @@ +using System.Collections.Immutable; + +namespace Roulette; + +internal class Wheel +{ + private static readonly ImmutableArray _slots = ImmutableArray.Create( + new Slot(Strings.Red(1), 1, 37, 40, 43, 46, 47), + new Slot(Strings.Black(2), 2, 37, 41, 43, 45, 48), + new Slot(Strings.Red(3), 3, 37, 42, 43, 46, 47), + new Slot(Strings.Black(4), 4, 37, 40, 43, 45, 48), + new Slot(Strings.Red(5), 5, 37, 41, 43, 46, 47), + new Slot(Strings.Black(6), 6, 37, 42, 43, 45, 48), + new Slot(Strings.Red(7), 7, 37, 40, 43, 46, 47), + new Slot(Strings.Black(8), 8, 37, 41, 43, 45, 48), + new Slot(Strings.Red(9), 9, 37, 42, 43, 46, 47), + new Slot(Strings.Black(10), 10, 37, 40, 43, 45, 48), + new Slot(Strings.Black(11), 11, 37, 41, 43, 46, 48), + new Slot(Strings.Red(12), 12, 37, 42, 43, 45, 47), + new Slot(Strings.Black(13), 13, 38, 40, 43, 46, 48), + new Slot(Strings.Red(14), 14, 38, 41, 43, 45, 47), + new Slot(Strings.Black(15), 15, 38, 42, 43, 46, 48), + new Slot(Strings.Red(16), 16, 38, 40, 43, 45, 47), + new Slot(Strings.Black(17), 17, 38, 41, 43, 46, 48), + new Slot(Strings.Red(18), 18, 38, 42, 43, 45, 47), + new Slot(Strings.Red(19), 19, 38, 40, 44, 46, 47), + new Slot(Strings.Black(20), 20, 38, 41, 44, 45, 48), + new Slot(Strings.Red(21), 21, 38, 42, 44, 46, 47), + new Slot(Strings.Black(22), 22, 38, 40, 44, 45, 48), + new Slot(Strings.Red(23), 23, 38, 41, 44, 46, 47), + new Slot(Strings.Black(24), 24, 38, 42, 44, 45, 48), + new Slot(Strings.Red(25), 25, 39, 40, 44, 46, 47), + new Slot(Strings.Black(26), 26, 39, 41, 44, 45, 48), + new Slot(Strings.Red(27), 27, 39, 42, 44, 46, 47), + new Slot(Strings.Black(28), 28, 39, 40, 44, 45, 48), + new Slot(Strings.Black(29), 29, 39, 41, 44, 46, 48), + new Slot(Strings.Red(30), 30, 39, 42, 44, 45, 47), + new Slot(Strings.Black(31), 31, 39, 40, 44, 46, 48), + new Slot(Strings.Red(32), 32, 39, 41, 44, 45, 47), + new Slot(Strings.Black(33), 33, 39, 42, 44, 46, 48), + new Slot(Strings.Red(34), 34, 39, 40, 44, 45, 47), + new Slot(Strings.Black(35), 35, 39, 41, 44, 46, 48), + new Slot(Strings.Red(36), 36, 39, 42, 44, 45, 47), + new Slot("0", 49), + new Slot("00", 50)); + + private readonly IRandom _random; + + public Wheel(IRandom random) => _random = random; + + public Slot Spin() => _slots[_random.Next(_slots.Length)]; +} From 4722cbded8d3d39b76e74c839a9b8608a20812e6 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Thu, 16 Feb 2023 18:02:04 +1100 Subject: [PATCH 021/141] Code cleanup --- 75_Roulette/csharp/Game.cs | 23 +++++++++++++---------- 75_Roulette/csharp/Table.cs | 23 ++++++----------------- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/75_Roulette/csharp/Game.cs b/75_Roulette/csharp/Game.cs index aa077042..75a6d7e5 100644 --- a/75_Roulette/csharp/Game.cs +++ b/75_Roulette/csharp/Game.cs @@ -1,5 +1,3 @@ -using System.Diagnostics.CodeAnalysis; - namespace Roulette; internal class Game @@ -7,14 +5,14 @@ internal class Game private readonly IReadWrite _io; private readonly IRandom _random; private readonly Table _table; - private readonly Croupier _house; + private readonly Croupier _croupier; public Game(IReadWrite io, IRandom random) { _io = io; _random = random; - _house = new(); - _table = new(_house, io, random); + _croupier = new(); + _table = new(_croupier, io, random); } public void Play() @@ -27,13 +25,18 @@ internal class Game while (_table.Play()); - if (!_house.PlayerIsBroke) - { - _house.CutCheck(_io, _random); - } - else + if (_croupier.PlayerIsBroke) { + _io.Write(Streams.LastDollar); _io.Write(Streams.Thanks); + return; } + + if (_croupier.HouseIsBroke) + { + _io.Write(Streams.BrokeHouse); + } + + _croupier.CutCheck(_io, _random); } } diff --git a/75_Roulette/csharp/Table.cs b/75_Roulette/csharp/Table.cs index fbc6341e..026152d1 100644 --- a/75_Roulette/csharp/Table.cs +++ b/75_Roulette/csharp/Table.cs @@ -4,11 +4,11 @@ internal class Table { private readonly IReadWrite _io; private readonly Wheel _wheel; - private readonly Croupier _house; + private readonly Croupier _croupier; - public Table(Croupier house, IReadWrite io, IRandom random) + public Table(Croupier croupier, IReadWrite io, IRandom random) { - _house = house; + _croupier = croupier; _io = io; _wheel = new(random); } @@ -19,20 +19,9 @@ internal class Table var slot = SpinWheel(); SettleBets(bets, slot); - _io.Write(_house.Totals); + _io.Write(_croupier.Totals); - if (_house.PlayerIsBroke) - { - _io.Write(Streams.LastDollar); - _io.Write(Streams.Thanks); - return false; - } - - if (_house.HouseIsBroke) - { - _io.Write(Streams.BrokeHouse); - return false; - } + if (_croupier.PlayerIsBroke || _croupier.HouseIsBroke) { return false; } return _io.ReadString(Prompts.Again).ToLowerInvariant().StartsWith('y'); } @@ -76,7 +65,7 @@ internal class Table { foreach (var bet in bets) { - _io.Write(slot.IsCoveredBy(bet) ? _house.Pay(bet) : _house.Take(bet)); + _io.Write(slot.IsCoveredBy(bet) ? _croupier.Pay(bet) : _croupier.Take(bet)); } } } From 846be86481c02fbae4460aa50a13612132f614ca Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Fri, 17 Feb 2023 07:52:45 +1100 Subject: [PATCH 022/141] Configure project --- 77_Salvo/csharp/Salvo.csproj | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/77_Salvo/csharp/Salvo.csproj b/77_Salvo/csharp/Salvo.csproj index d3fe4757..51470ec9 100644 --- a/77_Salvo/csharp/Salvo.csproj +++ b/77_Salvo/csharp/Salvo.csproj @@ -2,8 +2,16 @@ Exe net6.0 - 10 + 11 enable enable + + + + + + + + From e6425221bf3f63342d8f087de27c9190e8427906 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Sun, 19 Feb 2023 19:58:24 +1100 Subject: [PATCH 023/141] Create program structure --- 77_Salvo/csharp/Game.cs | 18 +++++++++++++++++ 77_Salvo/csharp/Program.cs | 6 ++++++ 77_Salvo/csharp/Resources/Resource.cs | 29 +++++++++++++++++++++++++++ 77_Salvo/csharp/Resources/Title.txt | 5 +++++ 4 files changed, 58 insertions(+) create mode 100644 77_Salvo/csharp/Game.cs create mode 100644 77_Salvo/csharp/Program.cs create mode 100644 77_Salvo/csharp/Resources/Resource.cs create mode 100644 77_Salvo/csharp/Resources/Title.txt diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs new file mode 100644 index 00000000..e3efb88c --- /dev/null +++ b/77_Salvo/csharp/Game.cs @@ -0,0 +1,18 @@ +namespace Salvo; + +internal class Game +{ + private readonly IReadWrite _io; + private readonly IRandom _random; + + public Game(IReadWrite io, IRandom random) + { + _io = io; + _random = random; + } + + internal void Play() + { + _io.Write(Streams.Title); + } +} \ No newline at end of file diff --git a/77_Salvo/csharp/Program.cs b/77_Salvo/csharp/Program.cs new file mode 100644 index 00000000..d1fa1a27 --- /dev/null +++ b/77_Salvo/csharp/Program.cs @@ -0,0 +1,6 @@ +global using Games.Common.IO; +global using Games.Common.Randomness; +global using static Salvo.Resources.Resource; +using Salvo; + +new Game(new ConsoleIO(), new RandomNumberGenerator()).Play(); diff --git a/77_Salvo/csharp/Resources/Resource.cs b/77_Salvo/csharp/Resources/Resource.cs new file mode 100644 index 00000000..0cc55034 --- /dev/null +++ b/77_Salvo/csharp/Resources/Resource.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Salvo.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream Title => GetStream(); + } + + internal static class Prompts + { + } + + private static string GetPrompt([CallerMemberName] string? name = null) => GetString($"{name}Prompt"); + + private static string GetString([CallerMemberName] string? name = null) + { + using var stream = GetStream(name); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + private static Stream GetStream([CallerMemberName] string? name = null) => + Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(Resource).Namespace}.{name}.txt") + ?? throw new Exception($"Could not find embedded resource stream '{name}'."); +} \ No newline at end of file diff --git a/77_Salvo/csharp/Resources/Title.txt b/77_Salvo/csharp/Resources/Title.txt new file mode 100644 index 00000000..6b2ba9b3 --- /dev/null +++ b/77_Salvo/csharp/Resources/Title.txt @@ -0,0 +1,5 @@ + Salvo + Creative Computing Morristown, New Jersey + + + From e5530d67c8e2f00503a5b4e2f969915d8c2a7734 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Sun, 26 Feb 2023 23:40:45 +1100 Subject: [PATCH 024/141] Transliterate logic --- 77_Salvo/csharp/Game.cs | 411 +++++++++++++++++++++++++++++++++++++ 77_Salvo/csharp/Program.cs | 1 + 2 files changed, 412 insertions(+) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index e3efb88c..e7a7f818 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -1,3 +1,5 @@ +using Games.Common.Randomness; + namespace Salvo; internal class Game @@ -14,5 +16,414 @@ internal class Game internal void Play() { _io.Write(Streams.Title); + +L1040: var AA = new float[11,11]; + var BA = new float[11,11]; + var CA = new int[8]; + var DA = new int[8]; + var EA = new int[13]; + var FA = new int[13]; + var GA = new int[13]; + var HA = new float[13]; + var KA = new int[11,11]; +L1050: var Z8 = 0; +L1060: for (var W = 1; W <= 12; W++) + { +L1070: EA[W] = -1; +L1080: HA[W] = -1; + } +L1100: for (var X = 1; X <= 10; X++) + { +L1110: for (var Y = 1; Y <= 10; Y++) + { +L1120: BA[X,Y] = 0; + } + } +L1150: for (var X = 1; X <= 12; X++) + { +L1160: FA[X]=0; +L1170: GA[X]=0; + } +L1190: for (var X = 1; X <= 10; X++) + { +L1200: for (var Y = 1; Y <= 10; Y++) + { +L1210: AA[X,Y]=0; + } + } +L1240: for (var K = 4; K >= 1; K--) + { +L1250: var U6=0; +L1260: var (X, Y, V, V2) = L2910(); +L1270: int FNA(int K) => (5-K)*3-2*K/4+Math.Sign(K-1)-1; +L1280: int FNB(int K) => K+K/4-Math.Sign(K-1); +L1290: if (V+V2+V*V2 == 0) { goto L1260; } +L1300: if (Y+V*FNB(K)>10) { goto L1260; } +L1310: if (Y+V*FNB(K)<1) { goto L1260; } +L1320: if (X+V2*FNB(K)>10) { goto L1260; } +L1330: if (X+V2*FNB(K)<1) { goto L1260; } +L1340: U6=U6+1; +L1350: if (U6>25) { goto L1190; } +L1360: for (var Z = 0; Z <= FNB(K); Z++) + { +L1370: FA[Z+FNA(K)]=X+V2*Z; +L1380: GA[Z+FNA(K)]=Y+V*Z; + } +L1400: var U8=FNA(K); +L1405: if (U8>U8+FNB(K)) { goto L1460; } +L1410: for (var Z2 = U8; Z2 <= U8+FNB(K); Z2++) + { +L1415: if (U8<2) { continue; } +L1420: for (var Z3 = 1; Z3 <= U8-1; Z3++) + { +L1430: if (Math.Sqrt((FA[Z3]-FA[Z2])*(FA[Z3]-FA[Z2]) + (GA[Z3]-GA[Z2])*(GA[Z3]-GA[Z2])) < 3.59) { goto L1260; } + } + } +L1460: for (var Z = 0; Z <= FNB(K); Z++) + { +L1470: AA[FA[Z+U8],GA[Z+U8]]=.5F+Math.Sign(K-1)*(K-1.5F); + } + } +L1500: _io.WriteLine("ENTER COORDINATES FOR..."); +L1510: _io.WriteLine("BATTLESHIP"); +L1520: for (var X = 1; X <= 5; X++) + { + var (Y, Z) = _io.Read2Numbers(""); + BA[(int)Y, (int)Z] = 3; +L1540: BA[(int)Y, (int)Z] = 3; + } +L1560: _io.WriteLine("CRUISER"); +L1570: for (var X = 1; X <= 3; X++) + { + var (Y, Z) = _io.Read2Numbers(""); +L1590: BA[(int)Y, (int)Z]=2; + } +L1610: _io.WriteLine("DESTROYER"); +L1620: for (var X = 1; X <= 2; X++) + { + var (Y, Z) = _io.Read2Numbers(""); +L1640: BA[(int)Y, (int)Z]=1; + } +L1660: _io.WriteLine("DESTROYER"); +L1670: for (var X = 1; X <= 2; X++) + { + var (Y, Z) = _io.Read2Numbers(""); +L1690: BA[(int)Y, (int)Z]=.5F; + } +L1710: var JS = _io.ReadString("DO YOU WANT TO START"); +L1730: if (JS != "WHERE ARE YOUR SHIPS?") { goto L1890; } +L1740: _io.WriteLine("BATTLESHIP"); +L1750: for (var Z = 1; Z <= 5; Z++) + { +L1760: _io.WriteLine($" {FA[Z]} {GA[Z]} "); + } +L1780: _io.WriteLine("CRUISER"); +L1790: _io.WriteLine($" {FA[6]} {GA[6]} "); +L1800: _io.WriteLine($" {FA[7]} {GA[7]} "); +L1810: _io.WriteLine($" {FA[8]} {GA[8]} "); +L1820: _io.WriteLine("DESTROYER"); +L1830: _io.WriteLine($" {FA[9]} {GA[9]} "); +L1840: _io.WriteLine($" {FA[10]} {GA[10]} "); +L1850: _io.WriteLine("DESTROYER"); +L1860: _io.WriteLine($" {FA[11]} {GA[11]} "); +L1870: _io.WriteLine($" {FA[12]} {GA[12]} "); +L1880: goto L1710; +L1890: var C=0; +L1900: var KS = _io.ReadString("DO YOU WANT TO SEE MY SHOTS"); +L1920: _io.WriteLine(); +L1930: if (JS != "YES") { goto L2620; } +L1950: if (JS != "YES") { goto L1990; } +L1960: C=C+1; +L1970: _io.WriteLine(); +L1980: _io.WriteLine($"TURN {C}"); +L1990: var A=0; +L2000: for (var W = .5F; W <= 3; W += .5F) + { +L2010: for (var X = 1; X <= 10; X++) + { +L2020: for (var Y = 1; Y <= 10; Y++) + { +L2030: if (BA[X,Y] == W) { goto L2070; } + } + } +L2060: continue; +L2070: A=A+(int)(W+.5F); + } +L2090: for (var W = 1; W <= 7; W++) + { +L2100: CA[W] = 0; +L2110: DA[W] = 0; +L2120: FA[W] = 0; +L2130: GA[W] = 0; + } +L2150: var P3=0; +L2160: for (var X = 1; X <= 10; X++) + { +L2170: for (var Y = 1; Y <= 10; Y++) + { +L2180: if (AA[X,Y]>10) { continue; } +L2190: P3=P3+1; + } + } +L2220: _io.WriteLine($"YOU HAVE {A} SHOTS."); +L2230: if (P3 >= A) { goto L2260; } +L2240: _io.WriteLine("YOU HAVE MORE SHOTS THAN THERE ARE BLANK SQUARES."); +L2250: goto L2890; +L2260: if (A != 0) { goto L2290; } +L2270: _io.WriteLine("I HAVE WON."); +L2280: return; +L2290: for (var W = 1; W <= A; W++) + { +L2300: var (X, Y) = _io.Read2Numbers(""); +L2310: if (X != (int)X) { goto L2370; } +L2320: if (X > 10) { goto L2370; } +L2330: if (X < 1) { goto L2370; } +L2340: if (Y != (int)Y) { goto L2370; } +L2350: if (Y > 10) { goto L2370; } +L2360: if (Y >= 1) { goto L2390; } +L2370: _io.WriteLine("ILLEGAL, ENTER AGAIN."); +L2380: goto L2300; +L2390: if (AA[(int)X,(int)Y]>10) + { + _io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {AA[(int)X,(int)Y]-10}"); + goto L2300; + } +L2400: CA[W]=(int)X; +L2410: DA[W]=(int)Y; + } +L2460: for (var W = 1; W <= A; W++) + { +L2470: if (AA[CA[W],DA[W]] == 3) + { + _io.WriteLine("YOU HIT MY BATTLESHIP."); + goto L2510; + } +L2480: if (AA[CA[W],DA[W]] == 2) + { + _io.WriteLine("YOU HIT MY CRUISER."); + goto L2510; + } +L2490: if (AA[CA[W],DA[W]] == 1) + { + _io.WriteLine("YOU HIT MY DESTROYER."); + goto L2510; + } +L2500: if (AA[CA[W],DA[W]] == .5F) + { + + _io.WriteLine("YOU HIT MY DESTROYER."); + goto L2510; + } +L2510: AA[CA[W],DA[W]] = 10+C; + } +L2620: A = 0; +L2630: if (JS == "YES") { goto L2670; } +L2640: C = C + 1; +L2650: _io.WriteLine(); +L2660: _io.WriteLine($"TURN {C}"); +L2670: A = 0; +L2680: for (var W = .5F; W <= 3; W += .5F) + { +L2690: for (var X = 1; X <= 10; X++) + { +L2700: for (var Y = 1; Y <= 10; Y++) + { +L2710: if (AA[X,Y] == W) { goto L2750; } + } + } +L2740: continue; +L2750: A = A + (int)(W+.5F); + } +L2770: P3=0; +L2780: for (var X = 1; X <= 10; X++) + { +L2790: for (var Y = 1; Y <= 10; Y++) + { +L2800: if (AA[X,Y]>10) { continue; } +L2810: P3=P3+1; + } + } +L2840: _io.WriteLine($"I HAVE {A} SHOTS."); +L2850: if (P3>A) { goto L2880; } +L2860: _io.WriteLine("I HAVE MORE SHOTS THAN BLANK SQUARES."); +L2870: goto L2270; +L2880: if (A != 0) { goto L2960; } +L2890: _io.WriteLine("YOU HAVE WON."); +L2900: return; + + (int, int, int, int) L2910() + { + var X = _random.Next(1, 11); + var Y = _random.Next(1, 11); + var V = _random.Next(-1, 2); + var V2 = _random.Next(-1, 2); + + return (X, Y, V, V2); + } + +L2960: for (var W = 1; W <= 12; W++) + { +L2970: if (HA[W]>0) { goto L3800; } + } +L3000: var w=0; +L3010: var R3=0; +L3020: var (x, y, v, v2) = L2910(); +L3030: //RESTORE + var index = 0; +L3040: var R2=0; +L3050: R3=R3+1; +L3060: if (R3>100) { goto L3010; } +L3070: if (x>10) { goto L3110; } +L3080: if (x>0) { goto L3120; } +L3090: x = 1 + (int)(_random.NextFloat() * 2.5F); +L3100: goto L3120; +L3110: x = 10 - (int)(_random.NextFloat() * 2.5F); +L3120: if (y>10) { goto L3160; } +L3130: if (y>0) { goto L3270; } +L3140: y=1+(int)(_random.NextFloat() * 2.5F); +L3150: goto L3270; +L3160: y=10-(int)(_random.NextFloat() * 2.5F); +L3170: goto L3270; +L3180: FA[w]=x; +L3190: GA[w]=y; +L3200: if (w==A) { goto L3380; } +L3210: if (R2==6) { goto L3030; } +L3240: //DATA 1,1,-1,1,1,-3,1,1,0,2,-1,1 + var data = new[] { 1,1,-1,1,1,-3,1,1,0,2,-1,1 }; +L3220: //READ X1,Y1 + var (X1, Y1) = (data[index++], data[index++]); +L3230: R2=R2+1; +L3250: x=x+X1; +L3260: y=y+Y1; +L3270: if (x>10) { goto L3210; } +L3280: if (x<1) { goto L3210; } +L3290: if (y>10) { goto L3210; } +L3300: if (y<1) { goto L3210; } +L3310: if (BA[x,y]>10) { goto L3210; } +L3320: for (var Q9 = 1; Q9 <= w; Q9++) + { +L3330: if (FA[Q9] != x) { continue; } +L3340: if (GA[Q9] == y) { goto L3210; } + } +L3360: w=w+1; +L3370: goto L3180; +L3380: if (KS != "YES") { goto L3420; } +L3390: for (var Z5 = 1; Z5 <= A; Z5++) + { +L3400: _io.WriteLine($" {FA[Z5]} {GA[Z5]}"); + } +L3420: for (var W = 1; W <= A; W++) + { +L3430: if (BA[FA[W],GA[W]] == 3) + { + _io.WriteLine("I HIT YOUR BATTLESHIP"); + } + else if (BA[FA[W],GA[W]] == 2) + { + _io.WriteLine("I HIT YOUR CRUISER"); + } + else if (BA[FA[W],GA[W]] == 1) + { + _io.WriteLine("I HIT YOUR DESTROYER"); + } + else if (BA[FA[W],GA[W]] == .5F) + { + _io.WriteLine("I HIT YOUR DESTROYER"); + } + else + { + BA[FA[W],GA[W]]=10+C; + continue; + } +L3570: for (var Q = 1; Q <= 12; Q++) + { +L3580: if (EA[Q] != -1) { continue; } +L3590: EA[Q]=10+C; +L3600: HA[Q]=BA[FA[W],GA[W]]; +L3610: var M3=0; +L3620: for (var M2 = 1; M2 <= 12; M2++) + { +L3630: if (HA[M2] != HA[Q]) { continue; } +L3640: M3=M3+1; + } +L3660: if (M3 != (int)(HA[Q]+.5F)+1+(int)(HA[Q]+.5F)/3) { goto L3470; } +L3670: for (var M2 = 1; M2 <= 12; M2++) + { +L3680: if (HA[M2] != HA[Q]) { continue; } +L3690: EA[M2] = -1; +L3700: HA[M2] = -1; + } +L3720: goto L3470; + } +L3740: _io.WriteLine("PROGRAM ABORT:"); +L3750: for (var Q = 1; Q <= 12; Q++) + { +L3760: _io.WriteLine($"EA[{Q}] = {EA[Q]}"); +L3770: _io.WriteLine($"HA[{Q}] = {HA[Q]}"); + } + return; +L3470: BA[FA[W],GA[W]]=10+C; + } +L3490: goto L1950; +L3800: //REM************************USINGEARRAY +L3810: for (var R = 1; R <= 10; R++) + { +L3820: for (var S = 1; S <= 10; S++) + { + KA[R,S]=0; + } + } +L3860: for (var U = 1; U <= 12; U++) + { +L3870: if (EA[U]<10) { continue; } +L3880: for (var R = 1; R <= 10; R++) + { +L3890: for (var S = 1; S <= 10; S++) + { +L3900: if (BA[R,S]<10) { goto L3930; } +L3910: KA[R,S]=-10000000; +L3920: continue; +L3930: for (var M = Math.Sign(1-R); M <= Math.Sign(10-R); M++) + { +L3940: for (var N = Math.Sign(1-S); N <= Math.Sign(10-S); N++) + { +L3950: if (N+M+N*M==0) { continue; } +L3960: if (BA[R+M,S+N] != EA[U]) { continue; } +L3970: KA[R,S]=KA[R,S]+EA[U]-S*(int)(HA[U]+.5F); + } + } + } + } + } +L4030: for (var R = 1; R <= A; R++) + { +L4040: FA[R]=R; +L4050: GA[R]=R; + } +L4070: for (var R = 1; R <= 10; R++) + { +L4080: for (var S = 1; S <= 10; S++) + { +L4090: var Q9=1; +L4100: for (var M = 1; M <= A; M++) + { +L4110: if (KA[FA[M],GA[M]]>=KA[FA[Q9],GA[Q9]]) { continue; } +L4120: Q9=M; + } +L4131: if (R>A) { goto L4140; } +L4132: if (R==S) { goto L4210; } +L4140: if (KA[R,S] Date: Tue, 28 Feb 2023 11:37:54 +1300 Subject: [PATCH 025/141] started work on a rust implementation of super star trek --- 84_Super_Star_Trek/rust/Cargo.toml | 8 ++++++ 84_Super_Star_Trek/rust/src/main.rs | 34 ++++++++++++++++++++++ 84_Super_Star_Trek/rust/src/model.rs | 43 ++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 84_Super_Star_Trek/rust/Cargo.toml create mode 100644 84_Super_Star_Trek/rust/src/main.rs create mode 100644 84_Super_Star_Trek/rust/src/model.rs diff --git a/84_Super_Star_Trek/rust/Cargo.toml b/84_Super_Star_Trek/rust/Cargo.toml new file mode 100644 index 00000000..1ec69633 --- /dev/null +++ b/84_Super_Star_Trek/rust/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rust" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs new file mode 100644 index 00000000..673b6700 --- /dev/null +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -0,0 +1,34 @@ +use model::{Galaxy, GameStatus, Pos, Quadrant}; + +mod model; + +fn main() { + let mut galaxy = Galaxy::generate_new(); + // create the model + // start the loop + loop { + view(&galaxy); + galaxy = wait_for_command(&galaxy); + } + // rather than using a loop, recursion and passing the ownership might be better +} + +fn view(model: &Galaxy) { + match model.game_status { + GameStatus::ShortRangeScan => { + let quadrant = &model.quadrants[model.enterprise.sector.as_index()]; + render_quadrant(&model.enterprise.sector, quadrant) + } + } +} + +fn render_quadrant(enterprise_sector: &Pos, quadrant: &Quadrant) { + +} + +fn wait_for_command(galaxy: &Galaxy) -> Galaxy { + // listen for command from readline + // handle bad commands + // update model + Galaxy::generate_new() +} \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs new file mode 100644 index 00000000..9ca50189 --- /dev/null +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -0,0 +1,43 @@ + +pub struct Galaxy { + pub quadrants: Vec, + pub enterprise: Enterprise, + pub game_status: GameStatus +} + +pub struct Pos(u8, u8); + +impl Pos { + pub fn as_index(&self) -> usize { + (self.0 * 8 + self.1).into() + } +} + +pub struct Quadrant { + pub stars: Vec, + pub star_bases: Vec, + pub klingons: Vec +} + +pub struct Klingon { + pub sector: Pos +} + +pub struct Enterprise { + pub quadrant: Pos, + pub sector: Pos, +} + +pub enum GameStatus { + ShortRangeScan +} + +impl Galaxy { + pub fn generate_new() -> Self { + Galaxy { + quadrants: Vec::new(), + enterprise: Enterprise { quadrant: Pos(0,0), sector: Pos(0,0) }, + game_status: GameStatus::ShortRangeScan + } + } +} \ No newline at end of file From 92b4d60e84da5f0c310c22a1a4550352623cfb5a Mon Sep 17 00:00:00 2001 From: Christopher Date: Tue, 28 Feb 2023 12:36:29 +1300 Subject: [PATCH 026/141] work aligning with mvu, along with a render quadrant view function --- 84_Super_Star_Trek/rust/src/main.rs | 56 ++++++++++++++++++--------- 84_Super_Star_Trek/rust/src/model.rs | 3 +- 84_Super_Star_Trek/rust/src/update.rs | 15 +++++++ 84_Super_Star_Trek/rust/src/view.rs | 35 +++++++++++++++++ 4 files changed, 90 insertions(+), 19 deletions(-) create mode 100644 84_Super_Star_Trek/rust/src/update.rs create mode 100644 84_Super_Star_Trek/rust/src/view.rs diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 673b6700..86bc2d85 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -1,34 +1,54 @@ -use model::{Galaxy, GameStatus, Pos, Quadrant}; +use std::io::stdin; + +use model::{Galaxy, GameStatus}; +use update::Message; mod model; +mod view; +mod update; fn main() { let mut galaxy = Galaxy::generate_new(); - // create the model - // start the loop loop { - view(&galaxy); - galaxy = wait_for_command(&galaxy); + view::view(&galaxy); + let command = wait_for_command(&galaxy.game_status); + galaxy = update::update(command, galaxy) } - // rather than using a loop, recursion and passing the ownership might be better } -fn view(model: &Galaxy) { - match model.game_status { - GameStatus::ShortRangeScan => { - let quadrant = &model.quadrants[model.enterprise.sector.as_index()]; - render_quadrant(&model.enterprise.sector, quadrant) +fn wait_for_command(game_status: &GameStatus) -> Message { + let stdin = stdin(); + loop { + match game_status { + _ => { + println!("Command?"); + let mut buffer = String::new(); + if let Ok(_) = stdin.read_line(&mut buffer) { + let text = buffer.trim_end(); + if let Some(msg) = as_message(text, game_status) { + return msg + } + print_command_help(); + } + } } } } -fn render_quadrant(enterprise_sector: &Pos, quadrant: &Quadrant) { - +fn as_message(text: &str, game_status: &GameStatus) -> Option { + if text == "" { + return None + } + match game_status { + _ => { + match text { + "SRS" => Some(Message::RequestShortRangeScan), + _ => None + } + } + } } -fn wait_for_command(galaxy: &Galaxy) -> Galaxy { - // listen for command from readline - // handle bad commands - // update model - Galaxy::generate_new() +fn print_command_help() { + println!("valid commands are just SRS at the mo") } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 9ca50189..2a03bbd9 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -5,7 +5,8 @@ pub struct Galaxy { pub game_status: GameStatus } -pub struct Pos(u8, u8); +#[derive(PartialEq)] +pub struct Pos(pub u8, pub u8); impl Pos { pub fn as_index(&self) -> usize { diff --git a/84_Super_Star_Trek/rust/src/update.rs b/84_Super_Star_Trek/rust/src/update.rs new file mode 100644 index 00000000..cf30c83e --- /dev/null +++ b/84_Super_Star_Trek/rust/src/update.rs @@ -0,0 +1,15 @@ +use crate::model::{Galaxy, GameStatus}; + +pub enum Message { + RequestShortRangeScan +} + +pub fn update(message: Message, model: Galaxy) -> Galaxy { + match message { + Message::RequestShortRangeScan => + Galaxy { + game_status: GameStatus::ShortRangeScan, + ..model + } + } +} \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs new file mode 100644 index 00000000..2f6bdf22 --- /dev/null +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -0,0 +1,35 @@ +use crate::model::{Galaxy, GameStatus, Quadrant, Pos, Klingon}; + + +pub fn view(model: &Galaxy) { + match model.game_status { + GameStatus::ShortRangeScan => { + let quadrant = &model.quadrants[model.enterprise.sector.as_index()]; + render_quadrant(&model.enterprise.sector, quadrant) + } + } +} + +fn render_quadrant(enterprise_sector: &Pos, quadrant: &Quadrant) { + println!("{:-^33}", ""); + for y in 0..=7 { + for x in 0..=7 { + let pos = Pos(x, y); + if &pos == enterprise_sector { + print!("<*> ") + } else if quadrant.stars.contains(&pos) { + print!(" * ") + } else if quadrant.star_bases.contains(&pos) { + print!(">!< ") + } else if let Some(_) = find_klingon(&pos, &quadrant.klingons) { + print!("+K+ ") + } + } + print!("\n") + } + println!("{:-^33}", ""); +} + +fn find_klingon<'a>(sector: &Pos, klingons: &'a Vec) -> Option<&'a Klingon> { + klingons.into_iter().find(|k| &k.sector == sector) +} \ No newline at end of file From c35736c5c8cc61f9bf070e65ab776f73fa92b297 Mon Sep 17 00:00:00 2001 From: Christopher Date: Tue, 28 Feb 2023 13:28:08 +1300 Subject: [PATCH 027/141] enterprise now starts in a random quadrant and empty sector --- 84_Super_Star_Trek/rust/Cargo.toml | 1 + 84_Super_Star_Trek/rust/src/main.rs | 7 ++- 84_Super_Star_Trek/rust/src/model.rs | 85 +++++++++++++++++++++++++++- 84_Super_Star_Trek/rust/src/view.rs | 21 +++---- 4 files changed, 97 insertions(+), 17 deletions(-) diff --git a/84_Super_Star_Trek/rust/Cargo.toml b/84_Super_Star_Trek/rust/Cargo.toml index 1ec69633..3b1d02f5 100644 --- a/84_Super_Star_Trek/rust/Cargo.toml +++ b/84_Super_Star_Trek/rust/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +rand = "0.8.5" diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 86bc2d85..20cd7ea3 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -1,4 +1,4 @@ -use std::io::stdin; +use std::io::{stdin, stdout, Write}; use model::{Galaxy, GameStatus}; use update::Message; @@ -18,10 +18,13 @@ fn main() { fn wait_for_command(game_status: &GameStatus) -> Message { let stdin = stdin(); + let mut stdout = stdout(); loop { match game_status { _ => { - println!("Command?"); + print!("Command? "); + let _ = stdout.flush(); + let mut buffer = String::new(); if let Ok(_) = stdin.read_line(&mut buffer) { let text = buffer.trim_end(); diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 2a03bbd9..4468e6f9 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -1,3 +1,4 @@ +use rand::Rng; pub struct Galaxy { pub quadrants: Vec, @@ -14,9 +15,14 @@ impl Pos { } } +#[derive(PartialEq)] +pub enum SectorStatus { + Empty, Star, StarBase, Klingon +} + pub struct Quadrant { pub stars: Vec, - pub star_bases: Vec, + pub star_base: Option, pub klingons: Vec } @@ -35,10 +41,83 @@ pub enum GameStatus { impl Galaxy { pub fn generate_new() -> Self { + let quadrants = Self::generate_quadrants(); + + let mut rng = rand::thread_rng(); + let enterprise_quadrant = Pos(rng.gen_range(0..8), rng.gen_range(0..8)); + let enterprise_sector = quadrants[enterprise_quadrant.as_index()].find_empty_sector(); + Galaxy { - quadrants: Vec::new(), - enterprise: Enterprise { quadrant: Pos(0,0), sector: Pos(0,0) }, + quadrants: quadrants, + enterprise: Enterprise { quadrant: enterprise_quadrant, sector: enterprise_sector }, game_status: GameStatus::ShortRangeScan } } + + fn generate_quadrants() -> Vec { + let mut rng = rand::thread_rng(); + let mut result = Vec::new(); + for _ in 0..64 { + + let mut quadrant = Quadrant { stars: Vec::new(), star_base: None, klingons: Vec::new() }; + let star_count = rng.gen_range(0..=7); + for _ in 0..star_count { + quadrant.stars.push(quadrant.find_empty_sector()); + } + + if rng.gen::() > 0.96 { + quadrant.star_base = Some(quadrant.find_empty_sector()); + } + + let klingon_count = + match rng.gen::() { + n if n > 0.98 => 3, + n if n > 0.95 => 2, + n if n > 0.8 => 1, + _ => 0 + }; + for _ in 0..klingon_count { + quadrant.klingons.push(Klingon { sector: quadrant.find_empty_sector() }); + } + + result.push(quadrant); + } + result + } +} + +impl Quadrant { + pub fn sector_status(&self, sector: &Pos) -> SectorStatus { + if self.stars.contains(§or) { + SectorStatus::Star + } else if self.is_starbase(§or) { + SectorStatus::StarBase + } else if self.has_klingon(§or) { + SectorStatus::Klingon + } else { + SectorStatus::Empty + } + } + + fn is_starbase(&self, sector: &Pos) -> bool { + match &self.star_base { + None => false, + Some(p) => p == sector + } + } + + fn has_klingon(&self, sector: &Pos) -> bool { + let klingons = &self.klingons; + klingons.into_iter().find(|k| &k.sector == sector).is_some() + } + + fn find_empty_sector(&self) -> Pos { + let mut rng = rand::thread_rng(); + loop { + let pos = Pos(rng.gen_range(0..8), rng.gen_range(0..8)); + if self.sector_status(&pos) == SectorStatus::Empty { + return pos + } + } + } } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 2f6bdf22..4f0975cf 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -1,4 +1,4 @@ -use crate::model::{Galaxy, GameStatus, Quadrant, Pos, Klingon}; +use crate::model::{Galaxy, GameStatus, Quadrant, Pos, SectorStatus}; pub fn view(model: &Galaxy) { @@ -17,19 +17,16 @@ fn render_quadrant(enterprise_sector: &Pos, quadrant: &Quadrant) { let pos = Pos(x, y); if &pos == enterprise_sector { print!("<*> ") - } else if quadrant.stars.contains(&pos) { - print!(" * ") - } else if quadrant.star_bases.contains(&pos) { - print!(">!< ") - } else if let Some(_) = find_klingon(&pos, &quadrant.klingons) { - print!("+K+ ") - } + } else { + match quadrant.sector_status(&pos) { + SectorStatus::Star => print!(" * "), + SectorStatus::StarBase => print!(">!< "), + SectorStatus::Klingon => print!("+K+ "), + _ => print!(" "), + } + } } print!("\n") } println!("{:-^33}", ""); } - -fn find_klingon<'a>(sector: &Pos, klingons: &'a Vec) -> Option<&'a Klingon> { - klingons.into_iter().find(|k| &k.sector == sector) -} \ No newline at end of file From 31b9834a7c1967d56f5634329742c7bc9885a44f Mon Sep 17 00:00:00 2001 From: Christopher Date: Tue, 28 Feb 2023 13:53:38 +1300 Subject: [PATCH 028/141] work on nav command this involves architectural shifts --- 84_Super_Star_Trek/rust/src/main.rs | 62 +++++++++++++++++++-------- 84_Super_Star_Trek/rust/src/model.rs | 4 +- 84_Super_Star_Trek/rust/src/update.rs | 23 +++++++++- 84_Super_Star_Trek/rust/src/view.rs | 5 ++- 4 files changed, 73 insertions(+), 21 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 20cd7ea3..43e3045c 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -17,35 +17,63 @@ fn main() { } fn wait_for_command(game_status: &GameStatus) -> Message { - let stdin = stdin(); - let mut stdout = stdout(); loop { match game_status { - _ => { - print!("Command? "); - let _ = stdout.flush(); - - let mut buffer = String::new(); - if let Ok(_) = stdin.read_line(&mut buffer) { - let text = buffer.trim_end(); - if let Some(msg) = as_message(text, game_status) { - return msg - } - print_command_help(); + GameStatus::NeedDirectionForNav => { + let text = prompt("Course (1-9)?"); + if let Some(msg) = as_message(&text, game_status) { + return msg } + }, + GameStatus::NeedSpeedForNav(_) => { + let text = prompt("Warp Factor (0-8)?"); + if let Some(msg) = as_message(&text, game_status) { + return msg + } + }, + _ => { + let text = prompt("Command?"); + if let Some(msg) = as_message(&text, game_status) { + return msg + } + print_command_help(); } } } } -fn as_message(text: &str, game_status: &GameStatus) -> Option { - if text == "" { - return None +fn prompt(prompt: &str) -> String { + let stdin = stdin(); + let mut stdout = stdout(); + + print!("{prompt} "); + let _ = stdout.flush(); + + let mut buffer = String::new(); + if let Ok(_) = stdin.read_line(&mut buffer) { + return buffer.trim_end().into(); } + "".into() +} + +fn as_message(text: &str, game_status: &GameStatus) -> Option { match game_status { + GameStatus::NeedDirectionForNav => { + match text.parse::() { + Ok(n) if (n >= 1 && n <= 8) => Some(Message::DirectionForNav(n)), + _ => None + } + }, + GameStatus::NeedSpeedForNav(dir) => { + match text.parse::() { + Ok(n) if (n >= 1 && n <= 8) => Some(Message::DirectionAndSpeedForNav(*dir, n)), + _ => None + } + } _ => { match text { "SRS" => Some(Message::RequestShortRangeScan), + "NAV" => Some(Message::RequestNavigation), _ => None } } @@ -53,5 +81,5 @@ fn as_message(text: &str, game_status: &GameStatus) -> Option { } fn print_command_help() { - println!("valid commands are just SRS at the mo") + println!("valid commands are just SRS and NAV at the mo") } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 4468e6f9..a05d8566 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -36,7 +36,9 @@ pub struct Enterprise { } pub enum GameStatus { - ShortRangeScan + ShortRangeScan, + NeedDirectionForNav, + NeedSpeedForNav(u8), } impl Galaxy { diff --git a/84_Super_Star_Trek/rust/src/update.rs b/84_Super_Star_Trek/rust/src/update.rs index cf30c83e..5788c778 100644 --- a/84_Super_Star_Trek/rust/src/update.rs +++ b/84_Super_Star_Trek/rust/src/update.rs @@ -1,7 +1,10 @@ use crate::model::{Galaxy, GameStatus}; pub enum Message { - RequestShortRangeScan + RequestShortRangeScan, + RequestNavigation, + DirectionForNav (u8), + DirectionAndSpeedForNav (u8, u8), } pub fn update(message: Message, model: Galaxy) -> Galaxy { @@ -10,6 +13,24 @@ pub fn update(message: Message, model: Galaxy) -> Galaxy { Galaxy { game_status: GameStatus::ShortRangeScan, ..model + }, + Message::RequestNavigation => { + Galaxy { + game_status: GameStatus::NeedDirectionForNav, + ..model } + }, + Message::DirectionForNav(dir) => { + Galaxy { + game_status: GameStatus::NeedSpeedForNav(dir), + ..model + } + }, + Message::DirectionAndSpeedForNav(dir, speed) => { + Galaxy { + game_status: GameStatus::ShortRangeScan, + ..model + } + } } } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 4f0975cf..9527289d 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -5,8 +5,9 @@ pub fn view(model: &Galaxy) { match model.game_status { GameStatus::ShortRangeScan => { let quadrant = &model.quadrants[model.enterprise.sector.as_index()]; - render_quadrant(&model.enterprise.sector, quadrant) - } + render_quadrant(&model.enterprise.sector, quadrant); + }, + _ => () } } From 8903e77d838e21c221a482a450a7a16ced314fca Mon Sep 17 00:00:00 2001 From: Christopher Date: Tue, 28 Feb 2023 19:45:54 +1300 Subject: [PATCH 029/141] reduced down to command and response (collapsed MVU) --- 84_Super_Star_Trek/rust/Cargo.toml | 1 + .../rust/src/{view.rs => commands.rs} | 17 +--- 84_Super_Star_Trek/rust/src/main.rs | 80 +++++-------------- 84_Super_Star_Trek/rust/src/model.rs | 12 +-- 84_Super_Star_Trek/rust/src/update.rs | 36 --------- 5 files changed, 29 insertions(+), 117 deletions(-) rename 84_Super_Star_Trek/rust/src/{view.rs => commands.rs} (54%) delete mode 100644 84_Super_Star_Trek/rust/src/update.rs diff --git a/84_Super_Star_Trek/rust/Cargo.toml b/84_Super_Star_Trek/rust/Cargo.toml index 3b1d02f5..01457249 100644 --- a/84_Super_Star_Trek/rust/Cargo.toml +++ b/84_Super_Star_Trek/rust/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ctrlc = "3.2.5" rand = "0.8.5" diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/commands.rs similarity index 54% rename from 84_Super_Star_Trek/rust/src/view.rs rename to 84_Super_Star_Trek/rust/src/commands.rs index 9527289d..264f7909 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -1,22 +1,13 @@ -use crate::model::{Galaxy, GameStatus, Quadrant, Pos, SectorStatus}; +use crate::model::{Galaxy, Pos, SectorStatus}; +pub fn short_range_scan(model: &Galaxy) { + let quadrant = &model.quadrants[model.enterprise.sector.as_index()]; -pub fn view(model: &Galaxy) { - match model.game_status { - GameStatus::ShortRangeScan => { - let quadrant = &model.quadrants[model.enterprise.sector.as_index()]; - render_quadrant(&model.enterprise.sector, quadrant); - }, - _ => () - } -} - -fn render_quadrant(enterprise_sector: &Pos, quadrant: &Quadrant) { println!("{:-^33}", ""); for y in 0..=7 { for x in 0..=7 { let pos = Pos(x, y); - if &pos == enterprise_sector { + if &pos == &model.enterprise.sector { print!("<*> ") } else { match quadrant.sector_status(&pos) { diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 43e3045c..cc6948ac 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -1,44 +1,27 @@ -use std::io::{stdin, stdout, Write}; +use std::{io::{stdin, stdout, Write}, process::exit}; -use model::{Galaxy, GameStatus}; -use update::Message; +use model::Galaxy; mod model; -mod view; -mod update; +mod commands; fn main() { - let mut galaxy = Galaxy::generate_new(); - loop { - view::view(&galaxy); - let command = wait_for_command(&galaxy.game_status); - galaxy = update::update(command, galaxy) - } -} + ctrlc::set_handler(move || { exit(0) }) + .expect("Error setting Ctrl-C handler"); + + let galaxy = Galaxy::generate_new(); + // init ops, starting state and notes + commands::short_range_scan(&galaxy); -fn wait_for_command(game_status: &GameStatus) -> Message { loop { - match game_status { - GameStatus::NeedDirectionForNav => { - let text = prompt("Course (1-9)?"); - if let Some(msg) = as_message(&text, game_status) { - return msg - } - }, - GameStatus::NeedSpeedForNav(_) => { - let text = prompt("Warp Factor (0-8)?"); - if let Some(msg) = as_message(&text, game_status) { - return msg - } - }, - _ => { - let text = prompt("Command?"); - if let Some(msg) = as_message(&text, game_status) { - return msg - } - print_command_help(); - } + match prompt("Command?").as_str() { + "SRS" => commands::short_range_scan(&galaxy), + _ => print_command_help() } + + // process the next command, based on it render something or update the galaxy or whatever + // this would be: read command, and based on it run dedicated function + // the function might get passed a mutable reference to the galaxy } } @@ -56,30 +39,11 @@ fn prompt(prompt: &str) -> String { "".into() } -fn as_message(text: &str, game_status: &GameStatus) -> Option { - match game_status { - GameStatus::NeedDirectionForNav => { - match text.parse::() { - Ok(n) if (n >= 1 && n <= 8) => Some(Message::DirectionForNav(n)), - _ => None - } - }, - GameStatus::NeedSpeedForNav(dir) => { - match text.parse::() { - Ok(n) if (n >= 1 && n <= 8) => Some(Message::DirectionAndSpeedForNav(*dir, n)), - _ => None - } - } - _ => { - match text { - "SRS" => Some(Message::RequestShortRangeScan), - "NAV" => Some(Message::RequestNavigation), - _ => None - } - } - } -} - fn print_command_help() { println!("valid commands are just SRS and NAV at the mo") -} \ No newline at end of file +} + +// match text.parse::() { +// Ok(n) if (n >= 1 && n <= 8) => Some(Message::DirectionForNav(n)), +// _ => None +// } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index a05d8566..a0f80751 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -2,8 +2,7 @@ use rand::Rng; pub struct Galaxy { pub quadrants: Vec, - pub enterprise: Enterprise, - pub game_status: GameStatus + pub enterprise: Enterprise } #[derive(PartialEq)] @@ -35,12 +34,6 @@ pub struct Enterprise { pub sector: Pos, } -pub enum GameStatus { - ShortRangeScan, - NeedDirectionForNav, - NeedSpeedForNav(u8), -} - impl Galaxy { pub fn generate_new() -> Self { let quadrants = Self::generate_quadrants(); @@ -51,8 +44,7 @@ impl Galaxy { Galaxy { quadrants: quadrants, - enterprise: Enterprise { quadrant: enterprise_quadrant, sector: enterprise_sector }, - game_status: GameStatus::ShortRangeScan + enterprise: Enterprise { quadrant: enterprise_quadrant, sector: enterprise_sector } } } diff --git a/84_Super_Star_Trek/rust/src/update.rs b/84_Super_Star_Trek/rust/src/update.rs deleted file mode 100644 index 5788c778..00000000 --- a/84_Super_Star_Trek/rust/src/update.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::model::{Galaxy, GameStatus}; - -pub enum Message { - RequestShortRangeScan, - RequestNavigation, - DirectionForNav (u8), - DirectionAndSpeedForNav (u8, u8), -} - -pub fn update(message: Message, model: Galaxy) -> Galaxy { - match message { - Message::RequestShortRangeScan => - Galaxy { - game_status: GameStatus::ShortRangeScan, - ..model - }, - Message::RequestNavigation => { - Galaxy { - game_status: GameStatus::NeedDirectionForNav, - ..model - } - }, - Message::DirectionForNav(dir) => { - Galaxy { - game_status: GameStatus::NeedSpeedForNav(dir), - ..model - } - }, - Message::DirectionAndSpeedForNav(dir, speed) => { - Galaxy { - game_status: GameStatus::ShortRangeScan, - ..model - } - } - } -} \ No newline at end of file From 7c8c420d4435f8afcc52c5aba0ddcfd1c84f8ee6 Mon Sep 17 00:00:00 2001 From: Christopher Date: Tue, 28 Feb 2023 20:15:46 +1300 Subject: [PATCH 030/141] work on nav command under new model --- 84_Super_Star_Trek/rust/src/main.rs | 44 +++++++++++++++---- 84_Super_Star_Trek/rust/src/text_constants.rs | 2 + 2 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 84_Super_Star_Trek/rust/src/text_constants.rs diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index cc6948ac..3fcf9632 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -1,21 +1,25 @@ -use std::{io::{stdin, stdout, Write}, process::exit}; +use std::{io::{stdin, stdout, Write}, process::exit, str::FromStr}; use model::Galaxy; +use crate::text_constants::BAD_NAV; + mod model; mod commands; +mod text_constants; fn main() { ctrlc::set_handler(move || { exit(0) }) .expect("Error setting Ctrl-C handler"); - let galaxy = Galaxy::generate_new(); + let mut galaxy = Galaxy::generate_new(); // init ops, starting state and notes commands::short_range_scan(&galaxy); loop { match prompt("Command?").as_str() { "SRS" => commands::short_range_scan(&galaxy), + "NAV" => gather_dir_and_speed_then_move(&mut galaxy), _ => print_command_help() } @@ -25,11 +29,31 @@ fn main() { } } -fn prompt(prompt: &str) -> String { +fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy) { + let dir = prompt_value::("Course (1-9)?", 1, 9); + if dir.is_none() { + println!("{}", BAD_NAV); + return; + } + + let speed = prompt_value::("Course (1-9)?", 0.0, 8.0); + if speed.is_none() { + println!("{}", BAD_NAV); + return; + } + + let distance = (speed.unwrap() * 8.0) as i32; + // could be done with a step function - while distance > 0, move by digit. + // if passing a boundary, test for the next quadrant in that direction + // if present, change quadrant and move to border + // else stop. +} + +fn prompt(prompt_text: &str) -> String { let stdin = stdin(); let mut stdout = stdout(); - print!("{prompt} "); + print!("{prompt_text} "); let _ = stdout.flush(); let mut buffer = String::new(); @@ -39,11 +63,15 @@ fn prompt(prompt: &str) -> String { "".into() } +fn prompt_value(prompt_text: &str, min: T, max: T) -> Option { + let passed = prompt(prompt_text); + match passed.parse::() { + Ok(n) if (n >= min && n <= max) => Some(n), + _ => None + } +} + fn print_command_help() { println!("valid commands are just SRS and NAV at the mo") } -// match text.parse::() { -// Ok(n) if (n >= 1 && n <= 8) => Some(Message::DirectionForNav(n)), -// _ => None -// } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/text_constants.rs b/84_Super_Star_Trek/rust/src/text_constants.rs new file mode 100644 index 00000000..36339abe --- /dev/null +++ b/84_Super_Star_Trek/rust/src/text_constants.rs @@ -0,0 +1,2 @@ + +pub const BAD_NAV: &str = " Lt. Sulu reports, 'Incorrect course data, sir!'"; \ No newline at end of file From 183ec6fde3df1475622d32570491c710fdf4b4e0 Mon Sep 17 00:00:00 2001 From: Christopher Date: Tue, 28 Feb 2023 20:19:54 +1300 Subject: [PATCH 031/141] added a note on how to calculate --- 84_Super_Star_Trek/rust/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 3fcf9632..46e932c3 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -47,6 +47,8 @@ fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy) { // if passing a boundary, test for the next quadrant in that direction // if present, change quadrant and move to border // else stop. + // one way to sort this would be to convert current pos to a galaxy pos (e.g. sector.x, y * 8), + // add dist, then mod/divide to get quadrant and new sector } fn prompt(prompt_text: &str) -> String { From 615438a2676cf3e4dadf74e2a948d9f4b425b851 Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 1 Mar 2023 08:29:47 +1300 Subject: [PATCH 032/141] more work on nav, almost done basics! --- 84_Super_Star_Trek/rust/src/main.rs | 44 ++++++++++++++++--- 84_Super_Star_Trek/rust/src/model.rs | 44 ++++++++++++++++++- 84_Super_Star_Trek/rust/src/text_constants.rs | 2 - 3 files changed, 82 insertions(+), 8 deletions(-) delete mode 100644 84_Super_Star_Trek/rust/src/text_constants.rs diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 46e932c3..481d2378 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -1,12 +1,9 @@ use std::{io::{stdin, stdout, Write}, process::exit, str::FromStr}; -use model::Galaxy; - -use crate::text_constants::BAD_NAV; +use model::{Galaxy, Pos}; mod model; mod commands; -mod text_constants; fn main() { ctrlc::set_handler(move || { exit(0) }) @@ -30,6 +27,8 @@ fn main() { } fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy) { + const BAD_NAV: &str = " Lt. Sulu reports, 'Incorrect course data, sir!'"; + let dir = prompt_value::("Course (1-9)?", 1, 9); if dir.is_none() { println!("{}", BAD_NAV); @@ -42,7 +41,42 @@ fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy) { return; } - let distance = (speed.unwrap() * 8.0) as i32; + let distance = (speed.unwrap() * 8.0) as u8; + let galaxy_pos = galaxy.enterprise.quadrant * 8u8 + galaxy.enterprise.sector; + let (mut nx, mut ny) = galaxy_pos.translate(dir.unwrap(), distance); + + let mut hit_edge = false; + if nx < 0 { + nx = 0; + hit_edge = true; + } + if ny < 0 { + ny = 0; + hit_edge = true; + } + if nx >= 64 { + ny = 63; + hit_edge = true; + } + if nx >= 64 { + ny = 63; + hit_edge = true; + } + + let new_quadrant = Pos((nx / 8) as u8, (ny / 8) as u8); + let new_sector = Pos((nx % 8) as u8, (ny % 8) as u8); + + if hit_edge { + println!("Lt. Uhura report message from Starfleet Command: + 'Permission to attempt crossing of galactic perimeter + is hereby *Denied*. Shut down your engines.' + Chief Engineer Scott reports, 'Warp engines shut down + at sector {} of quadrant {}.'", new_quadrant, new_sector); + } + + galaxy.enterprise.quadrant = new_quadrant; + galaxy.enterprise.sector = new_sector; + // could be done with a step function - while distance > 0, move by digit. // if passing a boundary, test for the next quadrant in that direction // if present, change quadrant and move to border diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index a0f80751..36ec9628 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -1,3 +1,5 @@ +use std::{ops::{Mul, Add}, fmt::Display}; + use rand::Rng; pub struct Galaxy { @@ -5,13 +7,53 @@ pub struct Galaxy { pub enterprise: Enterprise } -#[derive(PartialEq)] +#[derive(PartialEq, Clone, Copy)] pub struct Pos(pub u8, pub u8); impl Pos { + const DIRECTIONS : [(i8, i8); 8] = [ + (1, 0), + (1, -1), + (0, -1), + (-1, -1), + (-1, 0), + (-1, 1), + (0, 1), + (1, 1), + ]; + pub fn as_index(&self) -> usize { (self.0 * 8 + self.1).into() } + + pub fn translate(&self, dir: u8, dist: u8) -> (i8, i8) { + let (dx, dy): (i8, i8) = Self::DIRECTIONS[dir as usize]; + let x = (self.0 as i8) + dx * dist as i8; + let y = (self.1 as i8) + dy * dist as i8; + (x, y) + } +} + +impl Mul for Pos { + type Output = Self; + + fn mul(self, rhs: u8) -> Self::Output { + Pos(self.0 * rhs, self.1 * rhs) + } +} + +impl Add for Pos { + type Output = Self; + + fn add(self, rhs: Pos) -> Self::Output { + Pos(self.0 + rhs.0, self.1 + rhs.1) + } +} + +impl Display for Pos { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } } #[derive(PartialEq)] diff --git a/84_Super_Star_Trek/rust/src/text_constants.rs b/84_Super_Star_Trek/rust/src/text_constants.rs deleted file mode 100644 index 36339abe..00000000 --- a/84_Super_Star_Trek/rust/src/text_constants.rs +++ /dev/null @@ -1,2 +0,0 @@ - -pub const BAD_NAV: &str = " Lt. Sulu reports, 'Incorrect course data, sir!'"; \ No newline at end of file From 80ac05e0053f9a38eba2b8e0177afe81077cc9fb Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 1 Mar 2023 08:54:39 +1300 Subject: [PATCH 033/141] semi working nav (going weird directions) --- 84_Super_Star_Trek/rust/src/main.rs | 24 +++++++++++---------- 84_Super_Star_Trek/rust/src/model.rs | 31 +++++++++++----------------- 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 481d2378..a7265116 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -2,6 +2,8 @@ use std::{io::{stdin, stdout, Write}, process::exit, str::FromStr}; use model::{Galaxy, Pos}; +use crate::model::DIRECTIONS; + mod model; mod commands; @@ -14,7 +16,7 @@ fn main() { commands::short_range_scan(&galaxy); loop { - match prompt("Command?").as_str() { + match prompt("Command?").to_uppercase().as_str() { "SRS" => commands::short_range_scan(&galaxy), "NAV" => gather_dir_and_speed_then_move(&mut galaxy), _ => print_command_help() @@ -28,22 +30,25 @@ fn main() { fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy) { const BAD_NAV: &str = " Lt. Sulu reports, 'Incorrect course data, sir!'"; - + let dir = prompt_value::("Course (1-9)?", 1, 9); if dir.is_none() { println!("{}", BAD_NAV); return; } - let speed = prompt_value::("Course (1-9)?", 0.0, 8.0); + let speed = prompt_value::("Warp Factor (0-8)?", 0.0, 8.0); if speed.is_none() { println!("{}", BAD_NAV); return; } - let distance = (speed.unwrap() * 8.0) as u8; + let distance = (speed.unwrap() * 8.0) as i8; let galaxy_pos = galaxy.enterprise.quadrant * 8u8 + galaxy.enterprise.sector; - let (mut nx, mut ny) = galaxy_pos.translate(dir.unwrap(), distance); + + let (dx, dy): (i8, i8) = DIRECTIONS[(dir.unwrap() - 1) as usize]; + let mut nx = (galaxy_pos.0 as i8) + dx * distance; + let mut ny = (galaxy_pos.1 as i8) + dy * distance; let mut hit_edge = false; if nx < 0 { @@ -77,12 +82,9 @@ fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy) { galaxy.enterprise.quadrant = new_quadrant; galaxy.enterprise.sector = new_sector; - // could be done with a step function - while distance > 0, move by digit. - // if passing a boundary, test for the next quadrant in that direction - // if present, change quadrant and move to border - // else stop. - // one way to sort this would be to convert current pos to a galaxy pos (e.g. sector.x, y * 8), - // add dist, then mod/divide to get quadrant and new sector + // if new_quadrant isnt old quadrant print intro + + commands::short_range_scan(&galaxy) } fn prompt(prompt_text: &str) -> String { diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 36ec9628..2e097b9c 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -11,27 +11,9 @@ pub struct Galaxy { pub struct Pos(pub u8, pub u8); impl Pos { - const DIRECTIONS : [(i8, i8); 8] = [ - (1, 0), - (1, -1), - (0, -1), - (-1, -1), - (-1, 0), - (-1, 1), - (0, 1), - (1, 1), - ]; - pub fn as_index(&self) -> usize { (self.0 * 8 + self.1).into() } - - pub fn translate(&self, dir: u8, dist: u8) -> (i8, i8) { - let (dx, dy): (i8, i8) = Self::DIRECTIONS[dir as usize]; - let x = (self.0 as i8) + dx * dist as i8; - let y = (self.1 as i8) + dy * dist as i8; - (x, y) - } } impl Mul for Pos { @@ -52,10 +34,21 @@ impl Add for Pos { impl Display for Pos { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - todo!() + write!(f, "{} , {}", self.0, self.1) } } +pub const DIRECTIONS : [(i8, i8); 8] = [ + (1, 0), + (1, -1), + (0, -1), + (-1, -1), + (-1, 0), + (-1, 1), + (0, 1), + (1, 1), +]; + #[derive(PartialEq)] pub enum SectorStatus { Empty, Star, StarBase, Klingon From ab26776d61e60d20bf8fed252e60dc8f7453561d Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 1 Mar 2023 09:16:00 +1300 Subject: [PATCH 034/141] moved nav function bulk to commands module --- 84_Super_Star_Trek/rust/src/commands.rs | 48 ++++++++++++++++++++- 84_Super_Star_Trek/rust/src/main.rs | 57 +++---------------------- 84_Super_Star_Trek/rust/src/model.rs | 2 +- 3 files changed, 53 insertions(+), 54 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 264f7909..e87dd9c0 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -1,4 +1,4 @@ -use crate::model::{Galaxy, Pos, SectorStatus}; +use crate::model::{Galaxy, Pos, SectorStatus, COURSES}; pub fn short_range_scan(model: &Galaxy) { let quadrant = &model.quadrants[model.enterprise.sector.as_index()]; @@ -22,3 +22,49 @@ pub fn short_range_scan(model: &Galaxy) { } println!("{:-^33}", ""); } + +pub fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { + let distance = (warp_speed * 8.0) as i8; + let galaxy_pos = galaxy.enterprise.quadrant * 8u8 + galaxy.enterprise.sector; + + let (dx, dy): (i8, i8) = COURSES[(course - 1) as usize]; + + let mut nx = (galaxy_pos.0 as i8) + dx * distance; + let mut ny = (galaxy_pos.1 as i8) + dy * distance; + + let mut hit_edge = false; + if nx < 0 { + nx = 0; + hit_edge = true; + } + if ny < 0 { + ny = 0; + hit_edge = true; + } + if nx >= 64 { + ny = 63; + hit_edge = true; + } + if nx >= 64 { + ny = 63; + hit_edge = true; + } + + let new_quadrant = Pos((nx / 8) as u8, (ny / 8) as u8); + let new_sector = Pos((nx % 8) as u8, (ny % 8) as u8); + + if hit_edge { + println!("Lt. Uhura report message from Starfleet Command: + 'Permission to attempt crossing of galactic perimeter + is hereby *Denied*. Shut down your engines.' + Chief Engineer Scott reports, 'Warp engines shut down + at sector {} of quadrant {}.'", new_quadrant, new_sector); + } + + galaxy.enterprise.quadrant = new_quadrant; + galaxy.enterprise.sector = new_sector; + + // if new_quadrant isnt old quadrant print intro + + short_range_scan(&galaxy) +} diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index a7265116..a9b5f4ce 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -1,8 +1,6 @@ use std::{io::{stdin, stdout, Write}, process::exit, str::FromStr}; -use model::{Galaxy, Pos}; - -use crate::model::DIRECTIONS; +use model::Galaxy; mod model; mod commands; @@ -12,7 +10,7 @@ fn main() { .expect("Error setting Ctrl-C handler"); let mut galaxy = Galaxy::generate_new(); - // init ops, starting state and notes + // init options, starting state and notes commands::short_range_scan(&galaxy); loop { @@ -21,18 +19,14 @@ fn main() { "NAV" => gather_dir_and_speed_then_move(&mut galaxy), _ => print_command_help() } - - // process the next command, based on it render something or update the galaxy or whatever - // this would be: read command, and based on it run dedicated function - // the function might get passed a mutable reference to the galaxy } } fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy) { const BAD_NAV: &str = " Lt. Sulu reports, 'Incorrect course data, sir!'"; - let dir = prompt_value::("Course (1-9)?", 1, 9); - if dir.is_none() { + let course = prompt_value::("Course (1-9)?", 1, 9); + if course.is_none() { println!("{}", BAD_NAV); return; } @@ -43,48 +37,7 @@ fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy) { return; } - let distance = (speed.unwrap() * 8.0) as i8; - let galaxy_pos = galaxy.enterprise.quadrant * 8u8 + galaxy.enterprise.sector; - - let (dx, dy): (i8, i8) = DIRECTIONS[(dir.unwrap() - 1) as usize]; - let mut nx = (galaxy_pos.0 as i8) + dx * distance; - let mut ny = (galaxy_pos.1 as i8) + dy * distance; - - let mut hit_edge = false; - if nx < 0 { - nx = 0; - hit_edge = true; - } - if ny < 0 { - ny = 0; - hit_edge = true; - } - if nx >= 64 { - ny = 63; - hit_edge = true; - } - if nx >= 64 { - ny = 63; - hit_edge = true; - } - - let new_quadrant = Pos((nx / 8) as u8, (ny / 8) as u8); - let new_sector = Pos((nx % 8) as u8, (ny % 8) as u8); - - if hit_edge { - println!("Lt. Uhura report message from Starfleet Command: - 'Permission to attempt crossing of galactic perimeter - is hereby *Denied*. Shut down your engines.' - Chief Engineer Scott reports, 'Warp engines shut down - at sector {} of quadrant {}.'", new_quadrant, new_sector); - } - - galaxy.enterprise.quadrant = new_quadrant; - galaxy.enterprise.sector = new_sector; - - // if new_quadrant isnt old quadrant print intro - - commands::short_range_scan(&galaxy) + commands::move_enterprise(course.unwrap(), speed.unwrap(), galaxy); } fn prompt(prompt_text: &str) -> String { diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 2e097b9c..2ea85548 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -38,7 +38,7 @@ impl Display for Pos { } } -pub const DIRECTIONS : [(i8, i8); 8] = [ +pub const COURSES : [(i8, i8); 8] = [ (1, 0), (1, -1), (0, -1), From 09cb10eeb718528480f065744bf0bfc414007c28 Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 1 Mar 2023 09:39:08 +1300 Subject: [PATCH 035/141] added some unit tests for movement --- 84_Super_Star_Trek/rust/src/commands.rs | 128 ++++++++++++++++++++---- 84_Super_Star_Trek/rust/src/model.rs | 2 +- 2 files changed, 111 insertions(+), 19 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index e87dd9c0..485f57af 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -24,11 +24,41 @@ pub fn short_range_scan(model: &Galaxy) { } pub fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { - let distance = (warp_speed * 8.0) as i8; - let galaxy_pos = galaxy.enterprise.quadrant * 8u8 + galaxy.enterprise.sector; + + let end = find_end_quadrant_sector(galaxy.enterprise.quadrant, galaxy.enterprise.sector, course, warp_speed); + if end.hit_edge { + println!("Lt. Uhura report message from Starfleet Command: + 'Permission to attempt crossing of galactic perimeter + is hereby *Denied*. Shut down your engines.' + Chief Engineer Scott reports, 'Warp engines shut down + at sector {} of quadrant {}.'", end.quadrant, end.sector); + } + + galaxy.enterprise.quadrant = end.quadrant; + galaxy.enterprise.sector = end.sector; + + // if new_quadrant isnt old quadrant print intro + + short_range_scan(&galaxy) +} + +struct EndPosition { + quadrant: Pos, + sector: Pos, + hit_edge: bool +} + +fn find_end_quadrant_sector(start_quadrant: Pos, start_sector: Pos, course: u8, warp_speed: f32) -> EndPosition { let (dx, dy): (i8, i8) = COURSES[(course - 1) as usize]; + let mut distance = (warp_speed * 8.0) as i8; + if distance == 0 { + distance = 1; + } + + let galaxy_pos = start_quadrant * 8u8 + start_sector; + let mut nx = (galaxy_pos.0 as i8) + dx * distance; let mut ny = (galaxy_pos.1 as i8) + dy * distance; @@ -42,29 +72,91 @@ pub fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { hit_edge = true; } if nx >= 64 { - ny = 63; + nx = 63; hit_edge = true; } - if nx >= 64 { + if ny >= 64 { ny = 63; hit_edge = true; } - let new_quadrant = Pos((nx / 8) as u8, (ny / 8) as u8); - let new_sector = Pos((nx % 8) as u8, (ny % 8) as u8); + let quadrant = Pos((nx / 8) as u8, (ny / 8) as u8); + let sector = Pos((nx % 8) as u8, (ny % 8) as u8); - if hit_edge { - println!("Lt. Uhura report message from Starfleet Command: - 'Permission to attempt crossing of galactic perimeter - is hereby *Denied*. Shut down your engines.' - Chief Engineer Scott reports, 'Warp engines shut down - at sector {} of quadrant {}.'", new_quadrant, new_sector); + EndPosition { quadrant, sector, hit_edge } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_course_east() { + let start_quadrant = Pos(0,0); + let start_sector = Pos(0,0); + let end = find_end_quadrant_sector(start_quadrant, start_sector, 1, 0.1); + assert_eq!(end.quadrant, start_quadrant, "right quadrant"); + assert_eq!(end.sector, Pos(1,0), "right sector"); + assert!(!end.hit_edge) } - galaxy.enterprise.quadrant = new_quadrant; - galaxy.enterprise.sector = new_sector; - - // if new_quadrant isnt old quadrant print intro + #[test] + fn test_course_far_east() { + let start_quadrant = Pos(0,0); + let start_sector = Pos(0,0); + let end = find_end_quadrant_sector(start_quadrant, start_sector, 1, 1.0); + assert_eq!(end.quadrant, Pos(1,0), "right quadrant"); + assert_eq!(end.sector, start_sector, "right sector"); + assert!(!end.hit_edge) + } - short_range_scan(&galaxy) -} + #[test] + fn test_course_too_far_east() { + let start_quadrant = Pos(0,0); + let start_sector = Pos(0,0); + let end = find_end_quadrant_sector(start_quadrant, start_sector, 1, 8.0); + assert_eq!(end.quadrant, Pos(7,0), "right quadrant"); + assert_eq!(end.sector, Pos(7,0), "right sector"); + assert!(end.hit_edge) + } + + #[test] + fn test_course_south() { + let start_quadrant = Pos(0,0); + let start_sector = Pos(0,0); + let end = find_end_quadrant_sector(start_quadrant, start_sector, 7, 0.1); + assert_eq!(end.quadrant, start_quadrant, "right quadrant"); + assert_eq!(end.sector, Pos(0,1), "right sector"); + assert!(!end.hit_edge) + } + + #[test] + fn test_course_far_south() { + let start_quadrant = Pos(0,0); + let start_sector = Pos(0,0); + let end = find_end_quadrant_sector(start_quadrant, start_sector, 7, 1.0); + assert_eq!(end.quadrant, Pos(0,1), "right quadrant"); + assert_eq!(end.sector, start_sector, "right sector"); + assert!(!end.hit_edge) + } + + #[test] + fn test_course_too_far_south() { + let start_quadrant = Pos(0,0); + let start_sector = Pos(0,0); + let end = find_end_quadrant_sector(start_quadrant, start_sector, 7, 8.0); + assert_eq!(end.quadrant, Pos(0,7), "right quadrant"); + assert_eq!(end.sector, Pos(0,7), "right sector"); + assert!(end.hit_edge) + } + + #[test] + fn test_course_north_east() { + let start_quadrant = Pos(0,0); + let start_sector = Pos(0,1); + let end = find_end_quadrant_sector(start_quadrant, start_sector, 2, 0.1); + assert_eq!(end.quadrant, start_quadrant, "right quadrant"); + assert_eq!(end.sector, Pos(1,0), "right sector"); + assert!(!end.hit_edge) + } +} \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 2ea85548..fa0ca4c2 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -7,7 +7,7 @@ pub struct Galaxy { pub enterprise: Enterprise } -#[derive(PartialEq, Clone, Copy)] +#[derive(PartialEq, Clone, Copy, Debug)] pub struct Pos(pub u8, pub u8); impl Pos { From bc4470999c7f9499a43c50b90e1cc9e2ea2ed331 Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 1 Mar 2023 09:42:44 +1300 Subject: [PATCH 036/141] bug fix - used sector instead of quadrant as index in srs --- 84_Super_Star_Trek/rust/src/commands.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 485f57af..34c30136 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -1,7 +1,7 @@ use crate::model::{Galaxy, Pos, SectorStatus, COURSES}; pub fn short_range_scan(model: &Galaxy) { - let quadrant = &model.quadrants[model.enterprise.sector.as_index()]; + let quadrant = &model.quadrants[model.enterprise.quadrant.as_index()]; println!("{:-^33}", ""); for y in 0..=7 { From c23449fac320c6ccf3b4d3b44615ab62c12a7645 Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 1 Mar 2023 09:44:14 +1300 Subject: [PATCH 037/141] just reorged some code --- 84_Super_Star_Trek/rust/src/model.rs | 30 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index fa0ca4c2..d1c937ab 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -7,6 +7,21 @@ pub struct Galaxy { pub enterprise: Enterprise } +pub struct Quadrant { + pub stars: Vec, + pub star_base: Option, + pub klingons: Vec +} + +pub struct Klingon { + pub sector: Pos +} + +pub struct Enterprise { + pub quadrant: Pos, + pub sector: Pos, +} + #[derive(PartialEq, Clone, Copy, Debug)] pub struct Pos(pub u8, pub u8); @@ -54,21 +69,6 @@ pub enum SectorStatus { Empty, Star, StarBase, Klingon } -pub struct Quadrant { - pub stars: Vec, - pub star_base: Option, - pub klingons: Vec -} - -pub struct Klingon { - pub sector: Pos -} - -pub struct Enterprise { - pub quadrant: Pos, - pub sector: Pos, -} - impl Galaxy { pub fn generate_new() -> Self { let quadrants = Self::generate_quadrants(); From 60f0492c2814deaf619f1123e06c8f30417503c6 Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 1 Mar 2023 11:43:58 +1300 Subject: [PATCH 038/141] displaying stats properly aligned next to scan --- 84_Super_Star_Trek/rust/src/commands.rs | 15 +++++++++++++-- 84_Super_Star_Trek/rust/src/model.rs | 24 +++++++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 34c30136..100803a3 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -3,6 +3,17 @@ use crate::model::{Galaxy, Pos, SectorStatus, COURSES}; pub fn short_range_scan(model: &Galaxy) { let quadrant = &model.quadrants[model.enterprise.quadrant.as_index()]; + let data : [String; 8] = [ + format!("Stardate {}", model.stardate), + format!("Condition {:?}", model.enterprise.condition), + format!("Quadrant {}", model.enterprise.quadrant), + format!("Sector {}", model.enterprise.sector), + format!("Photon torpedoes {}", model.enterprise.photon_torpedoes), + format!("Total energy {}", model.enterprise.total_energy), + format!("Shields {}", model.enterprise.shields), + format!("Klingons remaining {}", model.remaining_klingons()), + ]; + println!("{:-^33}", ""); for y in 0..=7 { for x in 0..=7 { @@ -14,11 +25,11 @@ pub fn short_range_scan(model: &Galaxy) { SectorStatus::Star => print!(" * "), SectorStatus::StarBase => print!(">!< "), SectorStatus::Klingon => print!("+K+ "), - _ => print!(" "), + _ => print!(" "), } } } - print!("\n") + println!("{:>9}{}", "", data[y as usize]) } println!("{:-^33}", ""); } diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index d1c937ab..7fffd81f 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -3,6 +3,7 @@ use std::{ops::{Mul, Add}, fmt::Display}; use rand::Rng; pub struct Galaxy { + pub stardate: f32, pub quadrants: Vec, pub enterprise: Enterprise } @@ -18,8 +19,17 @@ pub struct Klingon { } pub struct Enterprise { + pub condition: Condition, pub quadrant: Pos, pub sector: Pos, + pub photon_torpedoes: u8, + pub total_energy: u16, + pub shields: u16, +} + +#[derive(Debug)] +pub enum Condition { + Green, Yellow, Red } #[derive(PartialEq, Clone, Copy, Debug)] @@ -70,6 +80,11 @@ pub enum SectorStatus { } impl Galaxy { + pub fn remaining_klingons(&self) -> u8 { + let quadrants = &self.quadrants; + quadrants.into_iter().map(|q| { q.klingons.len() as u8 }).sum::() + } + pub fn generate_new() -> Self { let quadrants = Self::generate_quadrants(); @@ -78,8 +93,15 @@ impl Galaxy { let enterprise_sector = quadrants[enterprise_quadrant.as_index()].find_empty_sector(); Galaxy { + stardate: 3800.0, quadrants: quadrants, - enterprise: Enterprise { quadrant: enterprise_quadrant, sector: enterprise_sector } + enterprise: Enterprise { + condition: Condition::Green, + quadrant: enterprise_quadrant, + sector: enterprise_sector, + photon_torpedoes: 28, + total_energy: 3000, + shields: 0 } } } From 4b326547e4d4441bd3c1099d29ae0683e6c5527b Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 1 Mar 2023 11:53:19 +1300 Subject: [PATCH 039/141] added a tasks tracking doc --- 84_Super_Star_Trek/rust/tasks.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 84_Super_Star_Trek/rust/tasks.md diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md new file mode 100644 index 00000000..3fcb826a --- /dev/null +++ b/84_Super_Star_Trek/rust/tasks.md @@ -0,0 +1,7 @@ +# Tasks + +Started after movement and display of stats was finished (no energy management or collision detection or anything). + +- [ ] stop before hitting an object +- [ ] remove energy on move +- [ ] klingon movement \ No newline at end of file From b56819aadffe74fb53d3dd8ab7de51a1cb161045 Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 1 Mar 2023 15:36:42 +1300 Subject: [PATCH 040/141] implemented taking damage and dying --- 84_Super_Star_Trek/rust/src/commands.rs | 4 +- 84_Super_Star_Trek/rust/src/main.rs | 27 ++++++++++++- 84_Super_Star_Trek/rust/src/model.rs | 50 ++++++++++++++++++++++--- 84_Super_Star_Trek/rust/tasks.md | 9 ++++- 4 files changed, 79 insertions(+), 11 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 100803a3..ffbdad61 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -1,4 +1,4 @@ -use crate::model::{Galaxy, Pos, SectorStatus, COURSES}; +use crate::model::{Galaxy, Pos, SectorStatus, COURSES, Quadrant}; pub fn short_range_scan(model: &Galaxy) { let quadrant = &model.quadrants[model.enterprise.quadrant.as_index()]; @@ -35,7 +35,7 @@ pub fn short_range_scan(model: &Galaxy) { } pub fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { - + let end = find_end_quadrant_sector(galaxy.enterprise.quadrant, galaxy.enterprise.sector, course, warp_speed); if end.hit_edge { diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index a9b5f4ce..9c2ea606 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -2,6 +2,8 @@ use std::{io::{stdin, stdout, Write}, process::exit, str::FromStr}; use model::Galaxy; +use crate::model::Condition; + mod model; mod commands; @@ -10,7 +12,7 @@ fn main() { .expect("Error setting Ctrl-C handler"); let mut galaxy = Galaxy::generate_new(); - // init options, starting state and notes + // todo: init options, starting state and notes commands::short_range_scan(&galaxy); loop { @@ -19,6 +21,14 @@ fn main() { "NAV" => gather_dir_and_speed_then_move(&mut galaxy), _ => print_command_help() } + + if galaxy.enterprise.condition == Condition::Destroyed { // todo: also check if stranded + println!("Is is stardate {}. + There were {} Klingon battle cruisers left at + the end of your mission. + ", galaxy.stardate, galaxy.remaining_klingons()); + break; + } } } @@ -37,6 +47,21 @@ fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy) { return; } + let quadrant = &mut galaxy.quadrants[galaxy.enterprise.quadrant.as_index()]; + for k in 0..quadrant.klingons.len() { + let new_sector = quadrant.find_empty_sector(); + quadrant.klingons[k].sector = new_sector; + } + + // todo: check if enterprise is protected by a starbase + + for k in 0..quadrant.klingons.len() { + quadrant.klingons[k].fire_on(&mut galaxy.enterprise); + } + + if galaxy.enterprise.condition == Condition::Destroyed { + return; + } commands::move_enterprise(course.unwrap(), speed.unwrap(), galaxy); } diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 7fffd81f..94e9128f 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -15,7 +15,21 @@ pub struct Quadrant { } pub struct Klingon { - pub sector: Pos + pub sector: Pos, + energy: f32 +} + +impl Klingon { + pub fn fire_on(&mut self, enterprise: &mut Enterprise) { + let mut rng = rand::thread_rng(); + let attack_strength = rng.gen::(); + let dist_to_enterprise = self.sector.abs_diff(enterprise.sector) as f32; + let hit_strength = self.energy * (2.0 + attack_strength) / dist_to_enterprise; + + self.energy /= 3.0 + attack_strength; + + enterprise.take_hit(self.sector, hit_strength as u16); + } } pub struct Enterprise { @@ -26,10 +40,30 @@ pub struct Enterprise { pub total_energy: u16, pub shields: u16, } +impl Enterprise { + fn take_hit(&mut self, sector: Pos, hit_strength: u16) { + if self.condition == Condition::Destroyed { + return; + } + + println!("{hit_strength} unit hit on Enterprise from sector {sector}"); -#[derive(Debug)] + // absorb into shields + + if self.shields <= 0 { + println!("The Enterprise has been destroyed. The Federation will be conquered."); + self.condition = Condition::Destroyed; + } + + // report shields + // take damage if strength is greater than 20 + } +} + +#[derive(PartialEq, Debug)] pub enum Condition { - Green, Yellow, Red + Green, Yellow, Red, + Destroyed, } #[derive(PartialEq, Clone, Copy, Debug)] @@ -39,6 +73,10 @@ impl Pos { pub fn as_index(&self) -> usize { (self.0 * 8 + self.1).into() } + + fn abs_diff(&self, other: Pos) -> u8 { + self.0.abs_diff(other.0) + self.1.abs_diff(other.1) + } } impl Mul for Pos { @@ -128,7 +166,7 @@ impl Galaxy { _ => 0 }; for _ in 0..klingon_count { - quadrant.klingons.push(Klingon { sector: quadrant.find_empty_sector() }); + quadrant.klingons.push(Klingon { sector: quadrant.find_empty_sector(), energy: rng.gen_range(100..=300) as f32 }); } result.push(quadrant); @@ -162,7 +200,7 @@ impl Quadrant { klingons.into_iter().find(|k| &k.sector == sector).is_some() } - fn find_empty_sector(&self) -> Pos { + pub fn find_empty_sector(&self) -> Pos { let mut rng = rand::thread_rng(); loop { let pos = Pos(rng.gen_range(0..8), rng.gen_range(0..8)); @@ -171,4 +209,4 @@ impl Quadrant { } } } -} \ No newline at end of file +} diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 3fcb826a..8a9951b5 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -2,6 +2,11 @@ Started after movement and display of stats was finished (no energy management or collision detection or anything). -- [ ] stop before hitting an object +- [x] klingon movement +- [x] klingon firing, game over etc - [ ] remove energy on move -- [ ] klingon movement \ No newline at end of file +- [ ] shields +- [ ] stranded... +- [ ] stop before hitting an object + - when moving across a sector, the enterprise should stop before it runs into something + - the current move is a jump, which makes this problematic. would need to rewrite it From 1732d950328b0f2ec723c554db5b0753d6ce06fe Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 1 Mar 2023 15:41:00 +1300 Subject: [PATCH 041/141] added proper help --- 84_Super_Star_Trek/rust/src/commands.rs | 16 +++++++++++++++- 84_Super_Star_Trek/rust/src/main.rs | 25 ++++++++++++------------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index ffbdad61..5cd90150 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -97,6 +97,20 @@ fn find_end_quadrant_sector(start_quadrant: Pos, start_sector: Pos, course: u8, EndPosition { quadrant, sector, hit_edge } } +pub fn move_klingons_and_fire(galaxy: &mut Galaxy) { + let quadrant = &mut galaxy.quadrants[galaxy.enterprise.quadrant.as_index()]; + for k in 0..quadrant.klingons.len() { + let new_sector = quadrant.find_empty_sector(); + quadrant.klingons[k].sector = new_sector; + } + + // todo: check if enterprise is protected by a starbase + + for k in 0..quadrant.klingons.len() { + quadrant.klingons[k].fire_on(&mut galaxy.enterprise); + } +} + #[cfg(test)] mod tests { use super::*; @@ -170,4 +184,4 @@ mod tests { assert_eq!(end.sector, Pos(1,0), "right sector"); assert!(!end.hit_edge) } -} \ No newline at end of file +} diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 9c2ea606..c91ca8bc 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -47,18 +47,7 @@ fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy) { return; } - let quadrant = &mut galaxy.quadrants[galaxy.enterprise.quadrant.as_index()]; - for k in 0..quadrant.klingons.len() { - let new_sector = quadrant.find_empty_sector(); - quadrant.klingons[k].sector = new_sector; - } - - // todo: check if enterprise is protected by a starbase - - for k in 0..quadrant.klingons.len() { - quadrant.klingons[k].fire_on(&mut galaxy.enterprise); - } - + commands::move_klingons_and_fire(galaxy); if galaxy.enterprise.condition == Condition::Destroyed { return; } @@ -88,6 +77,16 @@ fn prompt_value(prompt_text: &str, min: T, max: T) -> O } fn print_command_help() { - println!("valid commands are just SRS and NAV at the mo") + println!("Enter one of the following: + NAV (To set course) + SRS (For short range sensor scan) + LRS (For long range sensor scan) + PHA (To fire phasers) + TOR (To fire photon torpedoes) + SHE (To raise or lower shields) + DAM (For damage control reports) + COM (To call on library-computer) + XXX (To resign your command) + ") } From ec3b0697bb91927d8e94d1aee3f558d1cefa275f Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 1 Mar 2023 19:05:38 +1300 Subject: [PATCH 042/141] moved blobs of text into text_display mod --- 84_Super_Star_Trek/rust/src/commands.rs | 14 +------ 84_Super_Star_Trek/rust/src/main.rs | 28 +++----------- 84_Super_Star_Trek/rust/src/model.rs | 12 +++++- 84_Super_Star_Trek/rust/src/text_display.rs | 42 +++++++++++++++++++++ 4 files changed, 59 insertions(+), 37 deletions(-) create mode 100644 84_Super_Star_Trek/rust/src/text_display.rs diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 5cd90150..5251109e 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -1,4 +1,4 @@ -use crate::model::{Galaxy, Pos, SectorStatus, COURSES, Quadrant}; +use crate::{model::{Galaxy, Pos, SectorStatus, COURSES, Quadrant, EndPosition}, text_display}; pub fn short_range_scan(model: &Galaxy) { let quadrant = &model.quadrants[model.enterprise.quadrant.as_index()]; @@ -39,11 +39,7 @@ pub fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { let end = find_end_quadrant_sector(galaxy.enterprise.quadrant, galaxy.enterprise.sector, course, warp_speed); if end.hit_edge { - println!("Lt. Uhura report message from Starfleet Command: - 'Permission to attempt crossing of galactic perimeter - is hereby *Denied*. Shut down your engines.' - Chief Engineer Scott reports, 'Warp engines shut down - at sector {} of quadrant {}.'", end.quadrant, end.sector); + text_display::hit_edge(&end); } galaxy.enterprise.quadrant = end.quadrant; @@ -54,12 +50,6 @@ pub fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { short_range_scan(&galaxy) } -struct EndPosition { - quadrant: Pos, - sector: Pos, - hit_edge: bool -} - fn find_end_quadrant_sector(start_quadrant: Pos, start_sector: Pos, course: u8, warp_speed: f32) -> EndPosition { let (dx, dy): (i8, i8) = COURSES[(course - 1) as usize]; diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index c91ca8bc..bf6f5b96 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -6,6 +6,7 @@ use crate::model::Condition; mod model; mod commands; +mod text_display; fn main() { ctrlc::set_handler(move || { exit(0) }) @@ -19,31 +20,27 @@ fn main() { match prompt("Command?").to_uppercase().as_str() { "SRS" => commands::short_range_scan(&galaxy), "NAV" => gather_dir_and_speed_then_move(&mut galaxy), - _ => print_command_help() + _ => text_display::print_command_help() } if galaxy.enterprise.condition == Condition::Destroyed { // todo: also check if stranded - println!("Is is stardate {}. - There were {} Klingon battle cruisers left at - the end of your mission. - ", galaxy.stardate, galaxy.remaining_klingons()); + text_display::end_game_failure(&galaxy); break; } } } fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy) { - const BAD_NAV: &str = " Lt. Sulu reports, 'Incorrect course data, sir!'"; let course = prompt_value::("Course (1-9)?", 1, 9); if course.is_none() { - println!("{}", BAD_NAV); + text_display::bad_nav(); return; } let speed = prompt_value::("Warp Factor (0-8)?", 0.0, 8.0); if speed.is_none() { - println!("{}", BAD_NAV); + text_display::bad_nav(); return; } @@ -75,18 +72,3 @@ fn prompt_value(prompt_text: &str, min: T, max: T) -> O _ => None } } - -fn print_command_help() { - println!("Enter one of the following: - NAV (To set course) - SRS (For short range sensor scan) - LRS (For long range sensor scan) - PHA (To fire phasers) - TOR (To fire photon torpedoes) - SHE (To raise or lower shields) - DAM (For damage control reports) - COM (To call on library-computer) - XXX (To resign your command) - ") -} - diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 94e9128f..5026bdd9 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -2,6 +2,8 @@ use std::{ops::{Mul, Add}, fmt::Display}; use rand::Rng; +use crate::text_display; + pub struct Galaxy { pub stardate: f32, pub quadrants: Vec, @@ -46,12 +48,12 @@ impl Enterprise { return; } - println!("{hit_strength} unit hit on Enterprise from sector {sector}"); + text_display::enterprise_hit(&hit_strength, §or); // absorb into shields if self.shields <= 0 { - println!("The Enterprise has been destroyed. The Federation will be conquered."); + text_display::enterprise_destroyed(); self.condition = Condition::Destroyed; } @@ -66,6 +68,12 @@ pub enum Condition { Destroyed, } +pub struct EndPosition { + pub quadrant: Pos, + pub sector: Pos, + pub hit_edge: bool +} + #[derive(PartialEq, Clone, Copy, Debug)] pub struct Pos(pub u8, pub u8); diff --git a/84_Super_Star_Trek/rust/src/text_display.rs b/84_Super_Star_Trek/rust/src/text_display.rs new file mode 100644 index 00000000..17ffcab1 --- /dev/null +++ b/84_Super_Star_Trek/rust/src/text_display.rs @@ -0,0 +1,42 @@ +use crate::model::{Galaxy, Pos, EndPosition}; + +pub fn print_command_help() { + println!("Enter one of the following: + NAV (To set course) + SRS (For short range sensor scan) + LRS (For long range sensor scan) + PHA (To fire phasers) + TOR (To fire photon torpedoes) + SHE (To raise or lower shields) + DAM (For damage control reports) + COM (To call on library-computer) + XXX (To resign your command) + ") +} + +pub fn end_game_failure(galaxy: &Galaxy) { + println!("Is is stardate {}. +There were {} Klingon battle cruisers left at +the end of your mission. +", galaxy.stardate, galaxy.remaining_klingons()); +} + +pub fn enterprise_destroyed() { + println!("The Enterprise has been destroyed. The Federation will be conquered."); +} + +pub fn bad_nav() { + println!(" Lt. Sulu reports, 'Incorrect course data, sir!'") +} + +pub fn enterprise_hit(hit_strength: &u16, from_sector: &Pos) { + println!("{hit_strength} unit hit on Enterprise from sector {from_sector}"); +} + +pub fn hit_edge(end: &EndPosition) { + println!("Lt. Uhura report message from Starfleet Command: + 'Permission to attempt crossing of galactic perimeter + is hereby *Denied*. Shut down your engines.' + Chief Engineer Scott reports, 'Warp engines shut down + at sector {} of quadrant {}.'", end.quadrant, end.sector); +} \ No newline at end of file From efba9423969af07b3a2eac360c028f1e90fc2379 Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 1 Mar 2023 19:07:46 +1300 Subject: [PATCH 043/141] renamed text_display to view, and moved srs into it --- 84_Super_Star_Trek/rust/src/commands.rs | 40 +---------- 84_Super_Star_Trek/rust/src/main.rs | 14 ++-- 84_Super_Star_Trek/rust/src/model.rs | 6 +- 84_Super_Star_Trek/rust/src/text_display.rs | 42 ------------ 84_Super_Star_Trek/rust/src/view.rs | 76 +++++++++++++++++++++ 5 files changed, 89 insertions(+), 89 deletions(-) delete mode 100644 84_Super_Star_Trek/rust/src/text_display.rs create mode 100644 84_Super_Star_Trek/rust/src/view.rs diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 5251109e..c27c6505 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -1,45 +1,11 @@ -use crate::{model::{Galaxy, Pos, SectorStatus, COURSES, Quadrant, EndPosition}, text_display}; - -pub fn short_range_scan(model: &Galaxy) { - let quadrant = &model.quadrants[model.enterprise.quadrant.as_index()]; - - let data : [String; 8] = [ - format!("Stardate {}", model.stardate), - format!("Condition {:?}", model.enterprise.condition), - format!("Quadrant {}", model.enterprise.quadrant), - format!("Sector {}", model.enterprise.sector), - format!("Photon torpedoes {}", model.enterprise.photon_torpedoes), - format!("Total energy {}", model.enterprise.total_energy), - format!("Shields {}", model.enterprise.shields), - format!("Klingons remaining {}", model.remaining_klingons()), - ]; - - println!("{:-^33}", ""); - for y in 0..=7 { - for x in 0..=7 { - let pos = Pos(x, y); - if &pos == &model.enterprise.sector { - print!("<*> ") - } else { - match quadrant.sector_status(&pos) { - SectorStatus::Star => print!(" * "), - SectorStatus::StarBase => print!(">!< "), - SectorStatus::Klingon => print!("+K+ "), - _ => print!(" "), - } - } - } - println!("{:>9}{}", "", data[y as usize]) - } - println!("{:-^33}", ""); -} +use crate::{model::{Galaxy, Pos, COURSES, EndPosition}, view}; pub fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { let end = find_end_quadrant_sector(galaxy.enterprise.quadrant, galaxy.enterprise.sector, course, warp_speed); if end.hit_edge { - text_display::hit_edge(&end); + view::hit_edge(&end); } galaxy.enterprise.quadrant = end.quadrant; @@ -47,7 +13,7 @@ pub fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { // if new_quadrant isnt old quadrant print intro - short_range_scan(&galaxy) + view::short_range_scan(&galaxy) } fn find_end_quadrant_sector(start_quadrant: Pos, start_sector: Pos, course: u8, warp_speed: f32) -> EndPosition { diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index bf6f5b96..ec923c8c 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -6,7 +6,7 @@ use crate::model::Condition; mod model; mod commands; -mod text_display; +mod view; fn main() { ctrlc::set_handler(move || { exit(0) }) @@ -14,17 +14,17 @@ fn main() { let mut galaxy = Galaxy::generate_new(); // todo: init options, starting state and notes - commands::short_range_scan(&galaxy); + view::short_range_scan(&galaxy); loop { match prompt("Command?").to_uppercase().as_str() { - "SRS" => commands::short_range_scan(&galaxy), + "SRS" => view::short_range_scan(&galaxy), "NAV" => gather_dir_and_speed_then_move(&mut galaxy), - _ => text_display::print_command_help() + _ => view::print_command_help() } if galaxy.enterprise.condition == Condition::Destroyed { // todo: also check if stranded - text_display::end_game_failure(&galaxy); + view::end_game_failure(&galaxy); break; } } @@ -34,13 +34,13 @@ fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy) { let course = prompt_value::("Course (1-9)?", 1, 9); if course.is_none() { - text_display::bad_nav(); + view::bad_nav(); return; } let speed = prompt_value::("Warp Factor (0-8)?", 0.0, 8.0); if speed.is_none() { - text_display::bad_nav(); + view::bad_nav(); return; } diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 5026bdd9..2e54555c 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -2,7 +2,7 @@ use std::{ops::{Mul, Add}, fmt::Display}; use rand::Rng; -use crate::text_display; +use crate::view; pub struct Galaxy { pub stardate: f32, @@ -48,12 +48,12 @@ impl Enterprise { return; } - text_display::enterprise_hit(&hit_strength, §or); + view::enterprise_hit(&hit_strength, §or); // absorb into shields if self.shields <= 0 { - text_display::enterprise_destroyed(); + view::enterprise_destroyed(); self.condition = Condition::Destroyed; } diff --git a/84_Super_Star_Trek/rust/src/text_display.rs b/84_Super_Star_Trek/rust/src/text_display.rs deleted file mode 100644 index 17ffcab1..00000000 --- a/84_Super_Star_Trek/rust/src/text_display.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::model::{Galaxy, Pos, EndPosition}; - -pub fn print_command_help() { - println!("Enter one of the following: - NAV (To set course) - SRS (For short range sensor scan) - LRS (For long range sensor scan) - PHA (To fire phasers) - TOR (To fire photon torpedoes) - SHE (To raise or lower shields) - DAM (For damage control reports) - COM (To call on library-computer) - XXX (To resign your command) - ") -} - -pub fn end_game_failure(galaxy: &Galaxy) { - println!("Is is stardate {}. -There were {} Klingon battle cruisers left at -the end of your mission. -", galaxy.stardate, galaxy.remaining_klingons()); -} - -pub fn enterprise_destroyed() { - println!("The Enterprise has been destroyed. The Federation will be conquered."); -} - -pub fn bad_nav() { - println!(" Lt. Sulu reports, 'Incorrect course data, sir!'") -} - -pub fn enterprise_hit(hit_strength: &u16, from_sector: &Pos) { - println!("{hit_strength} unit hit on Enterprise from sector {from_sector}"); -} - -pub fn hit_edge(end: &EndPosition) { - println!("Lt. Uhura report message from Starfleet Command: - 'Permission to attempt crossing of galactic perimeter - is hereby *Denied*. Shut down your engines.' - Chief Engineer Scott reports, 'Warp engines shut down - at sector {} of quadrant {}.'", end.quadrant, end.sector); -} \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs new file mode 100644 index 00000000..5d8c1fd6 --- /dev/null +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -0,0 +1,76 @@ +use crate::model::{Galaxy, Pos, EndPosition, SectorStatus}; + +pub fn short_range_scan(model: &Galaxy) { + let quadrant = &model.quadrants[model.enterprise.quadrant.as_index()]; + + let data : [String; 8] = [ + format!("Stardate {}", model.stardate), + format!("Condition {:?}", model.enterprise.condition), + format!("Quadrant {}", model.enterprise.quadrant), + format!("Sector {}", model.enterprise.sector), + format!("Photon torpedoes {}", model.enterprise.photon_torpedoes), + format!("Total energy {}", model.enterprise.total_energy), + format!("Shields {}", model.enterprise.shields), + format!("Klingons remaining {}", model.remaining_klingons()), + ]; + + println!("{:-^33}", ""); + for y in 0..=7 { + for x in 0..=7 { + let pos = Pos(x, y); + if &pos == &model.enterprise.sector { + print!("<*> ") + } else { + match quadrant.sector_status(&pos) { + SectorStatus::Star => print!(" * "), + SectorStatus::StarBase => print!(">!< "), + SectorStatus::Klingon => print!("+K+ "), + _ => print!(" "), + } + } + } + println!("{:>9}{}", "", data[y as usize]) + } + println!("{:-^33}", ""); +} + +pub fn print_command_help() { + println!("Enter one of the following: + NAV (To set course) + SRS (For short range sensor scan) + LRS (For long range sensor scan) + PHA (To fire phasers) + TOR (To fire photon torpedoes) + SHE (To raise or lower shields) + DAM (For damage control reports) + COM (To call on library-computer) + XXX (To resign your command) + ") +} + +pub fn end_game_failure(galaxy: &Galaxy) { + println!("Is is stardate {}. +There were {} Klingon battle cruisers left at +the end of your mission. +", galaxy.stardate, galaxy.remaining_klingons()); +} + +pub fn enterprise_destroyed() { + println!("The Enterprise has been destroyed. The Federation will be conquered."); +} + +pub fn bad_nav() { + println!(" Lt. Sulu reports, 'Incorrect course data, sir!'") +} + +pub fn enterprise_hit(hit_strength: &u16, from_sector: &Pos) { + println!("{hit_strength} unit hit on Enterprise from sector {from_sector}"); +} + +pub fn hit_edge(end: &EndPosition) { + println!("Lt. Uhura report message from Starfleet Command: + 'Permission to attempt crossing of galactic perimeter + is hereby *Denied*. Shut down your engines.' + Chief Engineer Scott reports, 'Warp engines shut down + at sector {} of quadrant {}.'", end.quadrant, end.sector); +} From a18112767dfd0e587958623e5d0abe224ac439ac Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 1 Mar 2023 19:51:50 +1300 Subject: [PATCH 044/141] added intro section with quadrant name --- 84_Super_Star_Trek/rust/src/main.rs | 8 +++-- 84_Super_Star_Trek/rust/src/model.rs | 10 ++++++- 84_Super_Star_Trek/rust/src/view.rs | 45 ++++++++++++++++++++++++++++ 84_Super_Star_Trek/rust/tasks.md | 2 ++ 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index ec923c8c..8af85b77 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -1,4 +1,4 @@ -use std::{io::{stdin, stdout, Write}, process::exit, str::FromStr}; +use std::{io::{stdin, stdout, Write, Read}, process::exit, str::FromStr}; use model::Galaxy; @@ -13,7 +13,11 @@ fn main() { .expect("Error setting Ctrl-C handler"); let mut galaxy = Galaxy::generate_new(); - // todo: init options, starting state and notes + + view::intro(&galaxy); + let _ = prompt("Press Enter when ready to accept command"); + + view::starting_quadrant(&galaxy.enterprise.quadrant); view::short_range_scan(&galaxy); loop { diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 2e54555c..dd959220 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -6,6 +6,7 @@ use crate::view; pub struct Galaxy { pub stardate: f32, + pub final_stardate: f32, pub quadrants: Vec, pub enterprise: Enterprise } @@ -131,15 +132,22 @@ impl Galaxy { quadrants.into_iter().map(|q| { q.klingons.len() as u8 }).sum::() } + pub fn remaining_starbases(&self) -> u8 { + let quadrants = &self.quadrants; + quadrants.into_iter().filter(|q| q.star_base.is_some()).count() as u8 + } + pub fn generate_new() -> Self { let quadrants = Self::generate_quadrants(); let mut rng = rand::thread_rng(); let enterprise_quadrant = Pos(rng.gen_range(0..8), rng.gen_range(0..8)); let enterprise_sector = quadrants[enterprise_quadrant.as_index()].find_empty_sector(); + let stardate = rng.gen_range(20..=40) as f32 * 100.0; Galaxy { - stardate: 3800.0, + stardate, + final_stardate: stardate + rng.gen_range(25..=35) as f32, quadrants: quadrants, enterprise: Enterprise { condition: Condition::Green, diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 5d8c1fd6..62d22eea 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -1,5 +1,50 @@ use crate::model::{Galaxy, Pos, EndPosition, SectorStatus}; +pub fn intro(model: &Galaxy) { + let star_bases = model.remaining_starbases(); + let mut star_base_message: String = "There is 1 starbase".into(); + if star_bases > 1 { + star_base_message = format!("There are {} starbases", star_bases); + } + println!("Your orders are as follows: + Destroy the {} Klingon warships which have invaded + the galaxy before they can attack federation headquarters + on stardate {}. This gives you {} days. {} in the galaxy for resupplying your ship.", + model.remaining_klingons(), model.final_stardate, model.final_stardate - model.stardate, star_base_message) +} + +const REGION_NAMES: [&str; 16] = [ + "Antares", + "Sirius", + "Rigel", + "Deneb", + "Procyon", + "Capella", + "Vega", + "Betelgeuse", + "Canopus", + "Aldebaran", + "Altair", + "Regulus", + "Sagittarius", + "Arcturus", + "Pollux", + "Spica" +]; + +const SUB_REGION_NAMES: [&str; 4] = ["I", "II", "III", "IV"]; + +fn quadrant_name(quadrant: &Pos) -> String { + format!("{} {}", + REGION_NAMES[(quadrant.0 << 1 + quadrant.1 >> 1) as usize], + SUB_REGION_NAMES[(quadrant.1 % 4) as usize]) +} + +pub fn starting_quadrant(quadrant: &Pos) { + println!("Your mission begins with your starship located +in the galactic quadrant, '{}'.", quadrant_name(quadrant)) +} + pub fn short_range_scan(model: &Galaxy) { let quadrant = &model.quadrants[model.enterprise.quadrant.as_index()]; diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 8a9951b5..3da9cf93 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -4,6 +4,8 @@ Started after movement and display of stats was finished (no energy management o - [x] klingon movement - [x] klingon firing, game over etc +- [ ] add intro +- [ ] add entering (and starting in) sector headers - [ ] remove energy on move - [ ] shields - [ ] stranded... From 41ca9c3c709425809668f7ff9fbedd4dafdf06a2 Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 1 Mar 2023 20:36:01 +1300 Subject: [PATCH 045/141] added enterprise to intro, entering quadrant names, and fixed bug in name indexing --- 84_Super_Star_Trek/rust/src/commands.rs | 6 ++-- 84_Super_Star_Trek/rust/src/main.rs | 1 + 84_Super_Star_Trek/rust/src/model.rs | 2 +- 84_Super_Star_Trek/rust/src/view.rs | 40 ++++++++++++++++++++++--- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index c27c6505..00ab1377 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -8,10 +8,12 @@ pub fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { view::hit_edge(&end); } + if galaxy.enterprise.quadrant != end.quadrant { + view::enter_quadrant(&end.quadrant); + } + galaxy.enterprise.quadrant = end.quadrant; galaxy.enterprise.sector = end.sector; - - // if new_quadrant isnt old quadrant print intro view::short_range_scan(&galaxy) } diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 8af85b77..2c1c06be 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -14,6 +14,7 @@ fn main() { let mut galaxy = Galaxy::generate_new(); + view::enterprise(); view::intro(&galaxy); let _ = prompt("Press Enter when ready to accept command"); diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index dd959220..d79089dd 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -106,7 +106,7 @@ impl Add for Pos { impl Display for Pos { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{} , {}", self.0, self.1) + write!(f, "{} , {}", self.0 + 1, self.1 + 1) } } diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 62d22eea..774505f5 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -1,5 +1,33 @@ use crate::model::{Galaxy, Pos, EndPosition, SectorStatus}; +pub fn enterprise() { + println!(" + + + + + + + + + + + ,------*------, + ,------------- '--- ------' + '-------- --' / / + ,---' '-------/ /--, + '----------------' + + THE USS ENTERPRISE --- NCC-1701 + + + + + + +") +} + pub fn intro(model: &Galaxy) { let star_bases = model.remaining_starbases(); let mut star_base_message: String = "There is 1 starbase".into(); @@ -9,7 +37,7 @@ pub fn intro(model: &Galaxy) { println!("Your orders are as follows: Destroy the {} Klingon warships which have invaded the galaxy before they can attack federation headquarters - on stardate {}. This gives you {} days. {} in the galaxy for resupplying your ship.", + on stardate {}. This gives you {} days. {} in the galaxy for resupplying your ship.\n", model.remaining_klingons(), model.final_stardate, model.final_stardate - model.stardate, star_base_message) } @@ -36,13 +64,17 @@ const SUB_REGION_NAMES: [&str; 4] = ["I", "II", "III", "IV"]; fn quadrant_name(quadrant: &Pos) -> String { format!("{} {}", - REGION_NAMES[(quadrant.0 << 1 + quadrant.1 >> 1) as usize], + REGION_NAMES[((quadrant.0 << 1) + (quadrant.1 >> 2)) as usize], SUB_REGION_NAMES[(quadrant.1 % 4) as usize]) } pub fn starting_quadrant(quadrant: &Pos) { - println!("Your mission begins with your starship located -in the galactic quadrant, '{}'.", quadrant_name(quadrant)) + println!("\nYour mission begins with your starship located +in the galactic quadrant, '{}'.\n", quadrant_name(quadrant)) +} + +pub fn enter_quadrant(quadrant: &Pos) { + println!("\nNow entering {} quadrant . . .\n", quadrant_name(quadrant)) } pub fn short_range_scan(model: &Galaxy) { From dee8a96f3cd46f88b8f6da461c30c5c2e1307759 Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 1 Mar 2023 20:42:51 +1300 Subject: [PATCH 046/141] working on tasks --- 84_Super_Star_Trek/rust/tasks.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 3da9cf93..2b532d48 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -4,8 +4,9 @@ Started after movement and display of stats was finished (no energy management o - [x] klingon movement - [x] klingon firing, game over etc -- [ ] add intro -- [ ] add entering (and starting in) sector headers +- [x] add intro +- [x] add entering (and starting in) sector headers +- [ ] conditions and danger messages - [ ] remove energy on move - [ ] shields - [ ] stranded... From 4cda6be184beea9568bebd542aec192574886b2d Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 10:02:56 +1300 Subject: [PATCH 047/141] added warning messages when entering a sector --- 84_Super_Star_Trek/rust/src/commands.rs | 7 +++++++ 84_Super_Star_Trek/rust/src/main.rs | 6 ++---- 84_Super_Star_Trek/rust/src/model.rs | 16 ++++++---------- 84_Super_Star_Trek/rust/src/view.rs | 16 +++++++++++++++- 84_Super_Star_Trek/rust/tasks.md | 8 +++++++- 5 files changed, 37 insertions(+), 16 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 00ab1377..d55b08ff 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -10,6 +10,13 @@ pub fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { if galaxy.enterprise.quadrant != end.quadrant { view::enter_quadrant(&end.quadrant); + + if galaxy.quadrants[end.quadrant.as_index()].klingons.len() > 0 { + view::condition_red(); + if galaxy.enterprise.shields <= 200 { + view::danger_shields(); + } + } } galaxy.enterprise.quadrant = end.quadrant; diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 2c1c06be..498c5bd2 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -2,8 +2,6 @@ use std::{io::{stdin, stdout, Write, Read}, process::exit, str::FromStr}; use model::Galaxy; -use crate::model::Condition; - mod model; mod commands; mod view; @@ -28,7 +26,7 @@ fn main() { _ => view::print_command_help() } - if galaxy.enterprise.condition == Condition::Destroyed { // todo: also check if stranded + if galaxy.enterprise.destroyed { // todo: also check if stranded view::end_game_failure(&galaxy); break; } @@ -50,7 +48,7 @@ fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy) { } commands::move_klingons_and_fire(galaxy); - if galaxy.enterprise.condition == Condition::Destroyed { + if galaxy.enterprise.destroyed { return; } commands::move_enterprise(course.unwrap(), speed.unwrap(), galaxy); diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index d79089dd..9caa100a 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -36,7 +36,8 @@ impl Klingon { } pub struct Enterprise { - pub condition: Condition, + pub destroyed: bool, + pub damaged: bool, // later this could be by subsystem pub quadrant: Pos, pub sector: Pos, pub photon_torpedoes: u8, @@ -45,7 +46,7 @@ pub struct Enterprise { } impl Enterprise { fn take_hit(&mut self, sector: Pos, hit_strength: u16) { - if self.condition == Condition::Destroyed { + if self.destroyed { return; } @@ -55,7 +56,7 @@ impl Enterprise { if self.shields <= 0 { view::enterprise_destroyed(); - self.condition = Condition::Destroyed; + self.destroyed = true } // report shields @@ -63,12 +64,6 @@ impl Enterprise { } } -#[derive(PartialEq, Debug)] -pub enum Condition { - Green, Yellow, Red, - Destroyed, -} - pub struct EndPosition { pub quadrant: Pos, pub sector: Pos, @@ -150,7 +145,8 @@ impl Galaxy { final_stardate: stardate + rng.gen_range(25..=35) as f32, quadrants: quadrants, enterprise: Enterprise { - condition: Condition::Green, + destroyed: false, + damaged: false, quadrant: enterprise_quadrant, sector: enterprise_sector, photon_torpedoes: 28, diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 774505f5..320cb4eb 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -79,10 +79,16 @@ pub fn enter_quadrant(quadrant: &Pos) { pub fn short_range_scan(model: &Galaxy) { let quadrant = &model.quadrants[model.enterprise.quadrant.as_index()]; + let mut condition = "GREEN"; + if quadrant.klingons.len() > 0 { + condition = "*RED*"; + } else if model.enterprise.damaged { + condition = "YELLOW"; + } let data : [String; 8] = [ format!("Stardate {}", model.stardate), - format!("Condition {:?}", model.enterprise.condition), + format!("Condition {}", condition), format!("Quadrant {}", model.enterprise.quadrant), format!("Sector {}", model.enterprise.sector), format!("Photon torpedoes {}", model.enterprise.photon_torpedoes), @@ -151,3 +157,11 @@ pub fn hit_edge(end: &EndPosition) { Chief Engineer Scott reports, 'Warp engines shut down at sector {} of quadrant {}.'", end.quadrant, end.sector); } + +pub fn condition_red() { + println!("COMBAT AREA CONDITION RED") +} + +pub fn danger_shields() { + println!(" SHIELDS DANGEROUSLY LOW ") +} diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 2b532d48..374c1a29 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -6,10 +6,16 @@ Started after movement and display of stats was finished (no energy management o - [x] klingon firing, game over etc - [x] add intro - [x] add entering (and starting in) sector headers -- [ ] conditions and danger messages +- [x] conditions and danger messages - [ ] remove energy on move - [ ] shields + - [ ] shield control + - [ ] shield hit absorption +- [ ] subsystem damage + - and support for reports +- [ ] lrs? - [ ] stranded... - [ ] stop before hitting an object - when moving across a sector, the enterprise should stop before it runs into something - the current move is a jump, which makes this problematic. would need to rewrite it +- [ ] better command reading - support entering multiple values on a line (e.g. nav 3 0.1) From 7aec8284c03a866adff95e7040ee7e987d89d795 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 10:24:13 +1300 Subject: [PATCH 048/141] can now specify positional params on the nav command --- 84_Super_Star_Trek/rust/src/main.rs | 36 +++++++++++++++++++++-------- 84_Super_Star_Trek/rust/tasks.md | 2 +- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 498c5bd2..4212c6a7 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -20,9 +20,13 @@ fn main() { view::short_range_scan(&galaxy); loop { - match prompt("Command?").to_uppercase().as_str() { + let command = prompt("Command?"); + if command.len() == 0 { + continue; + } + match command[0].to_uppercase().as_str() { "SRS" => view::short_range_scan(&galaxy), - "NAV" => gather_dir_and_speed_then_move(&mut galaxy), + "NAV" => gather_dir_and_speed_then_move(&mut galaxy, command[1..].into()), _ => view::print_command_help() } @@ -33,15 +37,15 @@ fn main() { } } -fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy) { +fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy, provided: Vec) { - let course = prompt_value::("Course (1-9)?", 1, 9); + let course = param_or_prompt_value(&provided, 0, "Course (1-9)?", 1, 9); if course.is_none() { view::bad_nav(); return; } - let speed = prompt_value::("Warp Factor (0-8)?", 0.0, 8.0); + let speed = param_or_prompt_value(&provided, 1, "Warp Factor (0-8)?", 0.0, 8.0); if speed.is_none() { view::bad_nav(); return; @@ -54,7 +58,7 @@ fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy) { commands::move_enterprise(course.unwrap(), speed.unwrap(), galaxy); } -fn prompt(prompt_text: &str) -> String { +fn prompt(prompt_text: &str) -> Vec { let stdin = stdin(); let mut stdout = stdout(); @@ -63,15 +67,29 @@ fn prompt(prompt_text: &str) -> String { let mut buffer = String::new(); if let Ok(_) = stdin.read_line(&mut buffer) { - return buffer.trim_end().into(); + return buffer.trim_end().split(" ").map(|s| s.to_string()).collect(); } - "".into() + Vec::new() } fn prompt_value(prompt_text: &str, min: T, max: T) -> Option { let passed = prompt(prompt_text); - match passed.parse::() { + if passed.len() != 1 { + return None + } + match passed[0].parse::() { Ok(n) if (n >= min && n <= max) => Some(n), _ => None } } + +fn param_or_prompt_value(params: &Vec, param_pos: usize, prompt_text: &str, min: T, max: T) -> Option { + if params.len() > param_pos { + match params[param_pos].parse::() { + Ok(n) => Some(n), + _ => None + } + } else { + return prompt_value::(prompt_text, min, max); + } +} \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 374c1a29..da2b9c1b 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -18,4 +18,4 @@ Started after movement and display of stats was finished (no energy management o - [ ] stop before hitting an object - when moving across a sector, the enterprise should stop before it runs into something - the current move is a jump, which makes this problematic. would need to rewrite it -- [ ] better command reading - support entering multiple values on a line (e.g. nav 3 0.1) +- [x] better command reading - support entering multiple values on a line (e.g. nav 3 0.1) From ca89609c3e91470a959ac9a25533db80e8e4ce32 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 11:26:43 +1300 Subject: [PATCH 049/141] energy is now removed with travel --- 84_Super_Star_Trek/rust/src/commands.rs | 31 ++++++++++++++++++++----- 84_Super_Star_Trek/rust/src/model.rs | 3 ++- 84_Super_Star_Trek/rust/src/view.rs | 9 +++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index d55b08ff..3cc4aff5 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -2,25 +2,43 @@ use crate::{model::{Galaxy, Pos, COURSES, EndPosition}, view}; pub fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { - let end = find_end_quadrant_sector(galaxy.enterprise.quadrant, galaxy.enterprise.sector, course, warp_speed); + let ship = &mut galaxy.enterprise; + + // todo account for being blocked + + let end = find_end_quadrant_sector(ship.quadrant, ship.sector, course, warp_speed); + + // todo account for engine damage + + if end.energy_cost > ship.total_energy { + view::insuffient_warp_energy(warp_speed); + return + } if end.hit_edge { view::hit_edge(&end); } + - if galaxy.enterprise.quadrant != end.quadrant { + if ship.quadrant != end.quadrant { view::enter_quadrant(&end.quadrant); if galaxy.quadrants[end.quadrant.as_index()].klingons.len() > 0 { view::condition_red(); - if galaxy.enterprise.shields <= 200 { + if ship.shields <= 200 { view::danger_shields(); } } } - galaxy.enterprise.quadrant = end.quadrant; - galaxy.enterprise.sector = end.sector; + ship.quadrant = end.quadrant; + ship.sector = end.sector; + + ship.total_energy = (ship.total_energy - end.energy_cost).max(0); + if ship.shields > ship.total_energy { + view::divert_energy_from_shields(); + ship.shields = ship.total_energy; + } view::short_range_scan(&galaxy) } @@ -58,8 +76,9 @@ fn find_end_quadrant_sector(start_quadrant: Pos, start_sector: Pos, course: u8, let quadrant = Pos((nx / 8) as u8, (ny / 8) as u8); let sector = Pos((nx % 8) as u8, (ny % 8) as u8); + let energy_cost = distance as u16 + 10; - EndPosition { quadrant, sector, hit_edge } + EndPosition { quadrant, sector, hit_edge, energy_cost } } pub fn move_klingons_and_fire(galaxy: &mut Galaxy) { diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 9caa100a..103887f5 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -67,7 +67,8 @@ impl Enterprise { pub struct EndPosition { pub quadrant: Pos, pub sector: Pos, - pub hit_edge: bool + pub hit_edge: bool, + pub energy_cost: u16, } #[derive(PartialEq, Clone, Copy, Debug)] diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 320cb4eb..f460eaa7 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -165,3 +165,12 @@ pub fn condition_red() { pub fn danger_shields() { println!(" SHIELDS DANGEROUSLY LOW ") } + +pub fn insuffient_warp_energy(warp_speed: f32) { + println!("Engineering reports, 'Insufficient energy available + for maneuvering at warp {warp_speed} !'") +} + +pub fn divert_energy_from_shields() { + println!("Shield Control supplies energy to complete the maneuver.") +} From 2feb1a9c6532cd4c3dcbb93aa45aefc513989fad Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 11:37:58 +1300 Subject: [PATCH 050/141] implemented setting shields --- 84_Super_Star_Trek/rust/src/main.rs | 22 ++++++++++++++++++++++ 84_Super_Star_Trek/rust/src/view.rs | 17 +++++++++++++++++ 84_Super_Star_Trek/rust/tasks.md | 6 +++--- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 4212c6a7..477a0594 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -27,6 +27,7 @@ fn main() { match command[0].to_uppercase().as_str() { "SRS" => view::short_range_scan(&galaxy), "NAV" => gather_dir_and_speed_then_move(&mut galaxy, command[1..].into()), + "SHE" => get_amount_and_set_shields(&mut galaxy, command[1..].into()), _ => view::print_command_help() } @@ -37,6 +38,27 @@ fn main() { } } +fn get_amount_and_set_shields(galaxy: &mut Galaxy, provided: Vec) { + + // todo check for damaged module + + view::energy_available(galaxy.enterprise.total_energy); + let value = param_or_prompt_value(&provided, 0, "Number of units to shields", 0, i32::MAX); + if value.is_none() { + view::shields_unchanged(); + return; + } + let value = value.unwrap() as u16; + if value > galaxy.enterprise.total_energy { + view::ridiculous(); + view::shields_unchanged(); + return; + } + + galaxy.enterprise.shields = value; + view::shields_set(value); +} + fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy, provided: Vec) { let course = param_or_prompt_value(&provided, 0, "Course (1-9)?", 1, 9); diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index f460eaa7..95cad41d 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -174,3 +174,20 @@ pub fn insuffient_warp_energy(warp_speed: f32) { pub fn divert_energy_from_shields() { println!("Shield Control supplies energy to complete the maneuver.") } + +pub fn energy_available(total_energy: u16) { + println!("Energy available = {{{total_energy}}}") +} + +pub fn shields_unchanged() { + println!("") +} + +pub fn ridiculous() { + println!("Shield Control reports, 'This is not the Federation Treasury.'") +} + +pub fn shields_set(value: u16) { + println!("Deflector control room report: + 'Shields now at {value} units per your command.'") +} diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index da2b9c1b..7c7e1df8 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -7,9 +7,9 @@ Started after movement and display of stats was finished (no energy management o - [x] add intro - [x] add entering (and starting in) sector headers - [x] conditions and danger messages -- [ ] remove energy on move -- [ ] shields - - [ ] shield control +- [x] remove energy on move +- [x] shields + - [x] shield control - [ ] shield hit absorption - [ ] subsystem damage - and support for reports From 2898e701c3504192816172192e16c5c868e0cbd5 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 11:41:43 +1300 Subject: [PATCH 051/141] shield hit absorbtion --- 84_Super_Star_Trek/rust/src/model.rs | 4 ++-- 84_Super_Star_Trek/rust/src/view.rs | 4 ++++ 84_Super_Star_Trek/rust/tasks.md | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 103887f5..4f5a65ac 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -52,14 +52,14 @@ impl Enterprise { view::enterprise_hit(&hit_strength, §or); - // absorb into shields + self.shields = (self.shields - hit_strength).max(0); if self.shields <= 0 { view::enterprise_destroyed(); self.destroyed = true } - // report shields + view::shields_hit(self.shields); // take damage if strength is greater than 20 } } diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 95cad41d..02c72815 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -191,3 +191,7 @@ pub fn shields_set(value: u16) { println!("Deflector control room report: 'Shields now at {value} units per your command.'") } + +pub fn shields_hit(shields: u16) { + println!(" ") +} diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 7c7e1df8..918a0395 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -10,7 +10,7 @@ Started after movement and display of stats was finished (no energy management o - [x] remove energy on move - [x] shields - [x] shield control - - [ ] shield hit absorption + - [x] shield hit absorption - [ ] subsystem damage - and support for reports - [ ] lrs? From 5b560f929ce66cb0884f9df2b5eb2cb1864fa3e6 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 11:48:24 +1300 Subject: [PATCH 052/141] trimmed edge tests in nav, and removed no longer needed unit tests --- 84_Super_Star_Trek/rust/src/commands.rs | 95 +------------------------ 1 file changed, 3 insertions(+), 92 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 3cc4aff5..2f42edd5 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -56,23 +56,9 @@ fn find_end_quadrant_sector(start_quadrant: Pos, start_sector: Pos, course: u8, let mut nx = (galaxy_pos.0 as i8) + dx * distance; let mut ny = (galaxy_pos.1 as i8) + dy * distance; - let mut hit_edge = false; - if nx < 0 { - nx = 0; - hit_edge = true; - } - if ny < 0 { - ny = 0; - hit_edge = true; - } - if nx >= 64 { - nx = 63; - hit_edge = true; - } - if ny >= 64 { - ny = 63; - hit_edge = true; - } + let hit_edge = nx < 0 || ny < 0 || nx >= 64 || ny >= 64; + nx = nx.min(63).max(0); + ny = ny.min(63).max(0); let quadrant = Pos((nx / 8) as u8, (ny / 8) as u8); let sector = Pos((nx % 8) as u8, (ny % 8) as u8); @@ -94,78 +80,3 @@ pub fn move_klingons_and_fire(galaxy: &mut Galaxy) { quadrant.klingons[k].fire_on(&mut galaxy.enterprise); } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_course_east() { - let start_quadrant = Pos(0,0); - let start_sector = Pos(0,0); - let end = find_end_quadrant_sector(start_quadrant, start_sector, 1, 0.1); - assert_eq!(end.quadrant, start_quadrant, "right quadrant"); - assert_eq!(end.sector, Pos(1,0), "right sector"); - assert!(!end.hit_edge) - } - - #[test] - fn test_course_far_east() { - let start_quadrant = Pos(0,0); - let start_sector = Pos(0,0); - let end = find_end_quadrant_sector(start_quadrant, start_sector, 1, 1.0); - assert_eq!(end.quadrant, Pos(1,0), "right quadrant"); - assert_eq!(end.sector, start_sector, "right sector"); - assert!(!end.hit_edge) - } - - #[test] - fn test_course_too_far_east() { - let start_quadrant = Pos(0,0); - let start_sector = Pos(0,0); - let end = find_end_quadrant_sector(start_quadrant, start_sector, 1, 8.0); - assert_eq!(end.quadrant, Pos(7,0), "right quadrant"); - assert_eq!(end.sector, Pos(7,0), "right sector"); - assert!(end.hit_edge) - } - - #[test] - fn test_course_south() { - let start_quadrant = Pos(0,0); - let start_sector = Pos(0,0); - let end = find_end_quadrant_sector(start_quadrant, start_sector, 7, 0.1); - assert_eq!(end.quadrant, start_quadrant, "right quadrant"); - assert_eq!(end.sector, Pos(0,1), "right sector"); - assert!(!end.hit_edge) - } - - #[test] - fn test_course_far_south() { - let start_quadrant = Pos(0,0); - let start_sector = Pos(0,0); - let end = find_end_quadrant_sector(start_quadrant, start_sector, 7, 1.0); - assert_eq!(end.quadrant, Pos(0,1), "right quadrant"); - assert_eq!(end.sector, start_sector, "right sector"); - assert!(!end.hit_edge) - } - - #[test] - fn test_course_too_far_south() { - let start_quadrant = Pos(0,0); - let start_sector = Pos(0,0); - let end = find_end_quadrant_sector(start_quadrant, start_sector, 7, 8.0); - assert_eq!(end.quadrant, Pos(0,7), "right quadrant"); - assert_eq!(end.sector, Pos(0,7), "right sector"); - assert!(end.hit_edge) - } - - #[test] - fn test_course_north_east() { - let start_quadrant = Pos(0,0); - let start_sector = Pos(0,1); - let end = find_end_quadrant_sector(start_quadrant, start_sector, 2, 0.1); - assert_eq!(end.quadrant, start_quadrant, "right quadrant"); - assert_eq!(end.sector, Pos(1,0), "right sector"); - assert!(!end.hit_edge) - } -} From d7e3feff54689f5830a5c39d25ec263dde26f108 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 11:53:33 +1300 Subject: [PATCH 053/141] some reorganisation of code --- 84_Super_Star_Trek/rust/src/commands.rs | 48 ++++++++++++- 84_Super_Star_Trek/rust/src/input.rs | 37 ++++++++++ 84_Super_Star_Trek/rust/src/main.rs | 92 +++---------------------- 3 files changed, 91 insertions(+), 86 deletions(-) create mode 100644 84_Super_Star_Trek/rust/src/input.rs diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 2f42edd5..3239b45e 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -1,6 +1,48 @@ -use crate::{model::{Galaxy, Pos, COURSES, EndPosition}, view}; +use crate::{model::{Galaxy, Pos, COURSES, EndPosition}, view, input}; -pub fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { +pub fn get_amount_and_set_shields(galaxy: &mut Galaxy, provided: Vec) { + + // todo check for damaged module + + view::energy_available(galaxy.enterprise.total_energy); + let value = input::param_or_prompt_value(&provided, 0, "Number of units to shields", 0, i32::MAX); + if value.is_none() { + view::shields_unchanged(); + return; + } + let value = value.unwrap() as u16; + if value > galaxy.enterprise.total_energy { + view::ridiculous(); + view::shields_unchanged(); + return; + } + + galaxy.enterprise.shields = value; + view::shields_set(value); +} + +pub fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy, provided: Vec) { + + let course = input::param_or_prompt_value(&provided, 0, "Course (1-9)?", 1, 9); + if course.is_none() { + view::bad_nav(); + return; + } + + let speed = input::param_or_prompt_value(&provided, 1, "Warp Factor (0-8)?", 0.0, 8.0); + if speed.is_none() { + view::bad_nav(); + return; + } + + move_klingons_and_fire(galaxy); + if galaxy.enterprise.destroyed { + return; + } + move_enterprise(course.unwrap(), speed.unwrap(), galaxy); +} + +fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { let ship = &mut galaxy.enterprise; @@ -67,7 +109,7 @@ fn find_end_quadrant_sector(start_quadrant: Pos, start_sector: Pos, course: u8, EndPosition { quadrant, sector, hit_edge, energy_cost } } -pub fn move_klingons_and_fire(galaxy: &mut Galaxy) { +fn move_klingons_and_fire(galaxy: &mut Galaxy) { let quadrant = &mut galaxy.quadrants[galaxy.enterprise.quadrant.as_index()]; for k in 0..quadrant.klingons.len() { let new_sector = quadrant.find_empty_sector(); diff --git a/84_Super_Star_Trek/rust/src/input.rs b/84_Super_Star_Trek/rust/src/input.rs new file mode 100644 index 00000000..75f12102 --- /dev/null +++ b/84_Super_Star_Trek/rust/src/input.rs @@ -0,0 +1,37 @@ +use std::{io::{stdin, stdout, Write}, str::FromStr}; + +pub fn prompt(prompt_text: &str) -> Vec { + let stdin = stdin(); + let mut stdout = stdout(); + + print!("{prompt_text} "); + let _ = stdout.flush(); + + let mut buffer = String::new(); + if let Ok(_) = stdin.read_line(&mut buffer) { + return buffer.trim_end().split(" ").map(|s| s.to_string()).collect(); + } + Vec::new() +} + +pub fn prompt_value(prompt_text: &str, min: T, max: T) -> Option { + let passed = prompt(prompt_text); + if passed.len() != 1 { + return None + } + match passed[0].parse::() { + Ok(n) if (n >= min && n <= max) => Some(n), + _ => None + } +} + +pub fn param_or_prompt_value(params: &Vec, param_pos: usize, prompt_text: &str, min: T, max: T) -> Option { + if params.len() > param_pos { + match params[param_pos].parse::() { + Ok(n) => Some(n), + _ => None + } + } else { + return prompt_value::(prompt_text, min, max); + } +} \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 477a0594..1b9a0522 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -1,7 +1,8 @@ -use std::{io::{stdin, stdout, Write, Read}, process::exit, str::FromStr}; +use std::process::exit; use model::Galaxy; +mod input; mod model; mod commands; mod view; @@ -14,104 +15,29 @@ fn main() { view::enterprise(); view::intro(&galaxy); - let _ = prompt("Press Enter when ready to accept command"); + let _ = input::prompt("Press Enter when ready to accept command"); view::starting_quadrant(&galaxy.enterprise.quadrant); view::short_range_scan(&galaxy); loop { - let command = prompt("Command?"); + let command = input::prompt("Command?"); if command.len() == 0 { continue; } match command[0].to_uppercase().as_str() { "SRS" => view::short_range_scan(&galaxy), - "NAV" => gather_dir_and_speed_then_move(&mut galaxy, command[1..].into()), - "SHE" => get_amount_and_set_shields(&mut galaxy, command[1..].into()), + "NAV" => commands::gather_dir_and_speed_then_move(&mut galaxy, command[1..].into()), + "SHE" => commands::get_amount_and_set_shields(&mut galaxy, command[1..].into()), _ => view::print_command_help() } if galaxy.enterprise.destroyed { // todo: also check if stranded view::end_game_failure(&galaxy); + // todo check if can restart break; } + + // todo check for victory } } - -fn get_amount_and_set_shields(galaxy: &mut Galaxy, provided: Vec) { - - // todo check for damaged module - - view::energy_available(galaxy.enterprise.total_energy); - let value = param_or_prompt_value(&provided, 0, "Number of units to shields", 0, i32::MAX); - if value.is_none() { - view::shields_unchanged(); - return; - } - let value = value.unwrap() as u16; - if value > galaxy.enterprise.total_energy { - view::ridiculous(); - view::shields_unchanged(); - return; - } - - galaxy.enterprise.shields = value; - view::shields_set(value); -} - -fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy, provided: Vec) { - - let course = param_or_prompt_value(&provided, 0, "Course (1-9)?", 1, 9); - if course.is_none() { - view::bad_nav(); - return; - } - - let speed = param_or_prompt_value(&provided, 1, "Warp Factor (0-8)?", 0.0, 8.0); - if speed.is_none() { - view::bad_nav(); - return; - } - - commands::move_klingons_and_fire(galaxy); - if galaxy.enterprise.destroyed { - return; - } - commands::move_enterprise(course.unwrap(), speed.unwrap(), galaxy); -} - -fn prompt(prompt_text: &str) -> Vec { - let stdin = stdin(); - let mut stdout = stdout(); - - print!("{prompt_text} "); - let _ = stdout.flush(); - - let mut buffer = String::new(); - if let Ok(_) = stdin.read_line(&mut buffer) { - return buffer.trim_end().split(" ").map(|s| s.to_string()).collect(); - } - Vec::new() -} - -fn prompt_value(prompt_text: &str, min: T, max: T) -> Option { - let passed = prompt(prompt_text); - if passed.len() != 1 { - return None - } - match passed[0].parse::() { - Ok(n) if (n >= min && n <= max) => Some(n), - _ => None - } -} - -fn param_or_prompt_value(params: &Vec, param_pos: usize, prompt_text: &str, min: T, max: T) -> Option { - if params.len() > param_pos { - match params[param_pos].parse::() { - Ok(n) => Some(n), - _ => None - } - } else { - return prompt_value::(prompt_text, min, max); - } -} \ No newline at end of file From bcb1c68cb54293090b77253071226c33a0745f53 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 11:55:55 +1300 Subject: [PATCH 054/141] added some tasks --- 84_Super_Star_Trek/rust/tasks.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 918a0395..29f47cd3 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -19,3 +19,9 @@ Started after movement and display of stats was finished (no energy management o - when moving across a sector, the enterprise should stop before it runs into something - the current move is a jump, which makes this problematic. would need to rewrite it - [x] better command reading - support entering multiple values on a line (e.g. nav 3 0.1) +- [ ] starbases + - [ ] repair +- [ ] weapons + - [ ] phasers + - [ ] torpedoes +- [ ] restarting the game From 0581fe38f81b42bc81f8dd8d1b80b7ba80781aa8 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 13:36:30 +1300 Subject: [PATCH 055/141] basic damage to systems and restricted warp speed --- 84_Super_Star_Trek/rust/src/commands.rs | 33 ++++++++++++++++++++++--- 84_Super_Star_Trek/rust/src/main.rs | 6 ++--- 84_Super_Star_Trek/rust/src/model.rs | 6 ++--- 84_Super_Star_Trek/rust/src/view.rs | 25 ++++++++++++++++++- 4 files changed, 60 insertions(+), 10 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 3239b45e..0675ee4f 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -1,8 +1,20 @@ use crate::{model::{Galaxy, Pos, COURSES, EndPosition}, view, input}; +pub fn perform_short_range_scan(galaxy: &Galaxy) { + if galaxy.enterprise.damaged.contains_key(view::keys::SHORT_RANGE_SCAN) { + view::scanners_out(); + return; + } + + view::short_range_scan(&galaxy) +} + pub fn get_amount_and_set_shields(galaxy: &mut Galaxy, provided: Vec) { - // todo check for damaged module + if galaxy.enterprise.damaged.contains_key(view::keys::SHIELD_CONTROL) { + view::inoperable("Shield Control"); + return; + } view::energy_available(galaxy.enterprise.total_energy); let value = input::param_or_prompt_value(&provided, 0, "Number of units to shields", 0, i32::MAX); @@ -10,6 +22,7 @@ pub fn get_amount_and_set_shields(galaxy: &mut Galaxy, provided: Vec) { view::shields_unchanged(); return; } + let value = value.unwrap() as u16; if value > galaxy.enterprise.total_energy { view::ridiculous(); @@ -29,17 +42,31 @@ pub fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy, provided: Vec return; } - let speed = input::param_or_prompt_value(&provided, 1, "Warp Factor (0-8)?", 0.0, 8.0); + let course = course.unwrap(); + + let mut max_warp = 8.0; + if galaxy.enterprise.damaged.contains_key(view::keys::NAVIGATION) { + max_warp = 0.2; + } + + let speed = input::param_or_prompt_value(&provided, 1, format!("Warp Factor (0-{})?", max_warp).as_str(), 0.0, 8.0); if speed.is_none() { view::bad_nav(); return; } + + let speed = speed.unwrap(); + + if speed > max_warp { + view::damaged_engines(max_warp, speed); + return; + } move_klingons_and_fire(galaxy); if galaxy.enterprise.destroyed { return; } - move_enterprise(course.unwrap(), speed.unwrap(), galaxy); + move_enterprise(course, speed, galaxy); } fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 1b9a0522..ecde0476 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -26,9 +26,9 @@ fn main() { continue; } match command[0].to_uppercase().as_str() { - "SRS" => view::short_range_scan(&galaxy), - "NAV" => commands::gather_dir_and_speed_then_move(&mut galaxy, command[1..].into()), - "SHE" => commands::get_amount_and_set_shields(&mut galaxy, command[1..].into()), + view::keys::SHORT_RANGE_SCAN => commands::perform_short_range_scan(&galaxy), + view::keys::NAVIGATION => commands::gather_dir_and_speed_then_move(&mut galaxy, command[1..].into()), + view::keys::SHIELD_CONTROL => commands::get_amount_and_set_shields(&mut galaxy, command[1..].into()), _ => view::print_command_help() } diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 4f5a65ac..a078df05 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -1,4 +1,4 @@ -use std::{ops::{Mul, Add}, fmt::Display}; +use std::{ops::{Mul, Add}, fmt::Display, collections::HashMap}; use rand::Rng; @@ -37,7 +37,7 @@ impl Klingon { pub struct Enterprise { pub destroyed: bool, - pub damaged: bool, // later this could be by subsystem + pub damaged: HashMap, pub quadrant: Pos, pub sector: Pos, pub photon_torpedoes: u8, @@ -147,7 +147,7 @@ impl Galaxy { quadrants: quadrants, enterprise: Enterprise { destroyed: false, - damaged: false, + damaged: HashMap::new(), quadrant: enterprise_quadrant, sector: enterprise_sector, photon_torpedoes: 28, diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 02c72815..68934549 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -1,5 +1,15 @@ use crate::model::{Galaxy, Pos, EndPosition, SectorStatus}; +pub mod keys { + pub const SHORT_RANGE_SCAN: &str = "SRS"; + pub const NAVIGATION: &str = "NAV"; + pub const SHIELD_CONTROL: &str = "SHE"; + + pub const ALL_SYSTEMS: [&str; 3] = [ + SHORT_RANGE_SCAN, NAVIGATION, SHIELD_CONTROL + ]; +} + pub fn enterprise() { println!(" @@ -82,7 +92,7 @@ pub fn short_range_scan(model: &Galaxy) { let mut condition = "GREEN"; if quadrant.klingons.len() > 0 { condition = "*RED*"; - } else if model.enterprise.damaged { + } else if model.enterprise.damaged.len() > 0 { condition = "YELLOW"; } @@ -195,3 +205,16 @@ pub fn shields_set(value: u16) { pub fn shields_hit(shields: u16) { println!(" ") } + +pub fn inoperable(arg: &str) { + println!("{} inoperable", arg) +} + +pub fn scanners_out() { + println!("*** Short Range Sensors are out ***") +} + +pub fn damaged_engines(max_warp: f32, warp_factor: f32) { + println!("Warp engines are damaged. Maximum speed = warp {max_warp} + Chief Engineer Scott reports, 'The engines won't take warp {warp_factor} !'") +} From f253ff7155a140c5903f1a1bd742ad46f175d585 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 13:39:12 +1300 Subject: [PATCH 056/141] moved where system keys are defined --- 84_Super_Star_Trek/rust/src/commands.rs | 10 ++++------ 84_Super_Star_Trek/rust/src/main.rs | 6 +++--- 84_Super_Star_Trek/rust/src/model.rs | 10 ++++++++++ 84_Super_Star_Trek/rust/src/view.rs | 10 ---------- 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 0675ee4f..86c72f98 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -1,7 +1,7 @@ -use crate::{model::{Galaxy, Pos, COURSES, EndPosition}, view, input}; +use crate::{model::{Galaxy, Pos, COURSES, EndPosition, self}, view, input}; pub fn perform_short_range_scan(galaxy: &Galaxy) { - if galaxy.enterprise.damaged.contains_key(view::keys::SHORT_RANGE_SCAN) { + if galaxy.enterprise.damaged.contains_key(model::systems::SHORT_RANGE_SCAN) { view::scanners_out(); return; } @@ -11,7 +11,7 @@ pub fn perform_short_range_scan(galaxy: &Galaxy) { pub fn get_amount_and_set_shields(galaxy: &mut Galaxy, provided: Vec) { - if galaxy.enterprise.damaged.contains_key(view::keys::SHIELD_CONTROL) { + if galaxy.enterprise.damaged.contains_key(model::systems::SHIELD_CONTROL) { view::inoperable("Shield Control"); return; } @@ -45,7 +45,7 @@ pub fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy, provided: Vec let course = course.unwrap(); let mut max_warp = 8.0; - if galaxy.enterprise.damaged.contains_key(view::keys::NAVIGATION) { + if galaxy.enterprise.damaged.contains_key(model::systems::WARP_ENGINES) { max_warp = 0.2; } @@ -77,8 +77,6 @@ fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { let end = find_end_quadrant_sector(ship.quadrant, ship.sector, course, warp_speed); - // todo account for engine damage - if end.energy_cost > ship.total_energy { view::insuffient_warp_energy(warp_speed); return diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index ecde0476..5db8350c 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -26,9 +26,9 @@ fn main() { continue; } match command[0].to_uppercase().as_str() { - view::keys::SHORT_RANGE_SCAN => commands::perform_short_range_scan(&galaxy), - view::keys::NAVIGATION => commands::gather_dir_and_speed_then_move(&mut galaxy, command[1..].into()), - view::keys::SHIELD_CONTROL => commands::get_amount_and_set_shields(&mut galaxy, command[1..].into()), + model::systems::SHORT_RANGE_SCAN => commands::perform_short_range_scan(&galaxy), + model::systems::WARP_ENGINES => commands::gather_dir_and_speed_then_move(&mut galaxy, command[1..].into()), + model::systems::SHIELD_CONTROL => commands::get_amount_and_set_shields(&mut galaxy, command[1..].into()), _ => view::print_command_help() } diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index a078df05..f46cc548 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -64,6 +64,16 @@ impl Enterprise { } } +pub mod systems { + pub const SHORT_RANGE_SCAN: &str = "SRS"; + pub const WARP_ENGINES: &str = "NAV"; + pub const SHIELD_CONTROL: &str = "SHE"; + + pub const ALL: [&str; 3] = [ + SHORT_RANGE_SCAN, WARP_ENGINES, SHIELD_CONTROL + ]; +} + pub struct EndPosition { pub quadrant: Pos, pub sector: Pos, diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 68934549..e7bc5516 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -1,15 +1,5 @@ use crate::model::{Galaxy, Pos, EndPosition, SectorStatus}; -pub mod keys { - pub const SHORT_RANGE_SCAN: &str = "SRS"; - pub const NAVIGATION: &str = "NAV"; - pub const SHIELD_CONTROL: &str = "SHE"; - - pub const ALL_SYSTEMS: [&str; 3] = [ - SHORT_RANGE_SCAN, NAVIGATION, SHIELD_CONTROL - ]; -} - pub fn enterprise() { println!(" From cb685efe0cc32112242d4f657ee802505d2e6dc4 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 13:48:42 +1300 Subject: [PATCH 057/141] random system damage on hit --- 84_Super_Star_Trek/rust/src/commands.rs | 1 - 84_Super_Star_Trek/rust/src/model.rs | 17 +++++++++++++++++ 84_Super_Star_Trek/rust/tasks.md | 5 +++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 86c72f98..c5e59c12 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -86,7 +86,6 @@ fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { view::hit_edge(&end); } - if ship.quadrant != end.quadrant { view::enter_quadrant(&end.quadrant); diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index f46cc548..c968c08a 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -60,7 +60,24 @@ impl Enterprise { } view::shields_hit(self.shields); + // take damage if strength is greater than 20 + if hit_strength >= 20 { + self.take_damage(hit_strength) + } + } + + fn take_damage(&mut self, hit_strength: u16) { + let mut rng = rand::thread_rng(); + + let hit_past_shield = hit_strength as f32 / self.shields as f32; + if rng.gen::() > 0.6 || hit_past_shield < 0.02 { + return + } + + let system = systems::ALL[rng.gen_range(0..systems::ALL.len())].to_string(); + let damage = hit_past_shield + rng.gen::() * 0.5; + self.damaged.entry(system).and_modify(|d| *d += damage).or_insert(damage); } } diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 29f47cd3..c55914df 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -11,8 +11,9 @@ Started after movement and display of stats was finished (no energy management o - [x] shields - [x] shield control - [x] shield hit absorption -- [ ] subsystem damage - - and support for reports +- [x] subsystem damage + - [ ] and support for reports +- [ ] random system damage on move - [ ] lrs? - [ ] stranded... - [ ] stop before hitting an object From 33fc2b4c5a9a833fe73794e74a8c1c071c72afb5 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 14:11:49 +1300 Subject: [PATCH 058/141] implemented damage command --- 84_Super_Star_Trek/rust/src/commands.rs | 16 +++++++++++++++- 84_Super_Star_Trek/rust/src/main.rs | 1 + 84_Super_Star_Trek/rust/src/model.rs | 14 ++++++++++---- 84_Super_Star_Trek/rust/tasks.md | 2 +- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index c5e59c12..2bc5f4f6 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -1,4 +1,4 @@ -use crate::{model::{Galaxy, Pos, COURSES, EndPosition, self}, view, input}; +use crate::{model::{Galaxy, Pos, COURSES, EndPosition, self, Enterprise, systems}, view, input}; pub fn perform_short_range_scan(galaxy: &Galaxy) { if galaxy.enterprise.damaged.contains_key(model::systems::SHORT_RANGE_SCAN) { @@ -146,3 +146,17 @@ fn move_klingons_and_fire(galaxy: &mut Galaxy) { quadrant.klingons[k].fire_on(&mut galaxy.enterprise); } } + +pub fn display_damage_control(enterprise: &Enterprise) { + if enterprise.damaged.contains_key(model::systems::DAMAGE_CONTROL) { + view::inoperable("Damage Control"); + return; + } + + println!("Device State of Repair"); + for i in 0..systems::NAMES.len() { + let damage = enterprise.damaged.get(systems::ALL[i]).unwrap_or(&0.0); + println!("{:<25}{}", systems::NAMES[i], damage) + } + println!(); +} diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 5db8350c..0809ec23 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -29,6 +29,7 @@ fn main() { model::systems::SHORT_RANGE_SCAN => commands::perform_short_range_scan(&galaxy), model::systems::WARP_ENGINES => commands::gather_dir_and_speed_then_move(&mut galaxy, command[1..].into()), model::systems::SHIELD_CONTROL => commands::get_amount_and_set_shields(&mut galaxy, command[1..].into()), + model::systems::DAMAGE_CONTROL => commands::display_damage_control(&galaxy.enterprise), _ => view::print_command_help() } diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index c968c08a..675ac8ec 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -61,7 +61,6 @@ impl Enterprise { view::shields_hit(self.shields); - // take damage if strength is greater than 20 if hit_strength >= 20 { self.take_damage(hit_strength) } @@ -77,17 +76,24 @@ impl Enterprise { let system = systems::ALL[rng.gen_range(0..systems::ALL.len())].to_string(); let damage = hit_past_shield + rng.gen::() * 0.5; - self.damaged.entry(system).and_modify(|d| *d += damage).or_insert(damage); + self.damaged.entry(system).and_modify(|d| *d -= damage).or_insert(-damage); } } pub mod systems { + use std::collections::HashMap; + pub const SHORT_RANGE_SCAN: &str = "SRS"; pub const WARP_ENGINES: &str = "NAV"; pub const SHIELD_CONTROL: &str = "SHE"; + pub const DAMAGE_CONTROL: &str = "DAM"; - pub const ALL: [&str; 3] = [ - SHORT_RANGE_SCAN, WARP_ENGINES, SHIELD_CONTROL + pub const ALL: [&str; 4] = [ + SHORT_RANGE_SCAN, WARP_ENGINES, SHIELD_CONTROL, DAMAGE_CONTROL + ]; + + pub const NAMES: [&str; 4] = [ + "Short Range Scanners", "Warp Engines", "Shield Control", "Damage Control" ]; } diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index c55914df..e6c68ce1 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -12,7 +12,7 @@ Started after movement and display of stats was finished (no energy management o - [x] shield control - [x] shield hit absorption - [x] subsystem damage - - [ ] and support for reports + - [x] and support for reports - [ ] random system damage on move - [ ] lrs? - [ ] stranded... From 51663ea0b168b2ee6915b9b198a6b74ec864c9fb Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 17:13:52 +1300 Subject: [PATCH 059/141] implemented random system movement changes damage and repair, with changes to how this is implemented / reported --- 84_Super_Star_Trek/rust/src/commands.rs | 52 +++++++++++++++++++++++-- 84_Super_Star_Trek/rust/src/model.rs | 34 ++++++++++++---- 84_Super_Star_Trek/rust/src/view.rs | 16 ++++++++ 84_Super_Star_Trek/rust/tasks.md | 2 +- 4 files changed, 93 insertions(+), 11 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 2bc5f4f6..3b14e4cf 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -1,3 +1,5 @@ +use rand::Rng; + use crate::{model::{Galaxy, Pos, COURSES, EndPosition, self, Enterprise, systems}, view, input}; pub fn perform_short_range_scan(galaxy: &Galaxy) { @@ -66,9 +68,53 @@ pub fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy, provided: Vec if galaxy.enterprise.destroyed { return; } + + repair_systems(&mut galaxy.enterprise, speed); + repair_or_damage_random_system(&mut galaxy.enterprise); + move_enterprise(course, speed, galaxy); } +fn repair_systems(enterprise: &mut Enterprise, amount: f32) { + + let keys: Vec = enterprise.damaged.keys().map(|k| k.to_string()).collect(); + let mut repaired = Vec::new(); + for key in keys { + let fully_fixed = enterprise.repair_system(&key, amount); + if fully_fixed { + repaired.push(systems::name_for(&key)); + } + } + + if repaired.len() <= 0 { + return; + } + + view::damage_control_report(); + for name in repaired { + view::system_repair_completed(name); + } +} + +fn repair_or_damage_random_system(enterprise: &mut Enterprise) { + let mut rng = rand::thread_rng(); + + if rng.gen::() > 0.2 { + return; + } + + let system = systems::KEYS[rng.gen_range(0..systems::KEYS.len())].to_string(); + let system_name = &systems::name_for(&system); + + if rng.gen::() >= 0.6 { + enterprise.repair_system(&system, rng.gen::() * 3.0 + 1.0); + view::random_repair_report_for(system_name, false); + } else { + enterprise.damage_system(&system, rng.gen::() * 5.0 + 1.0); + view::random_repair_report_for(system_name, true); + } +} + fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { let ship = &mut galaxy.enterprise; @@ -154,9 +200,9 @@ pub fn display_damage_control(enterprise: &Enterprise) { } println!("Device State of Repair"); - for i in 0..systems::NAMES.len() { - let damage = enterprise.damaged.get(systems::ALL[i]).unwrap_or(&0.0); - println!("{:<25}{}", systems::NAMES[i], damage) + for key in systems::KEYS { + let damage = enterprise.damaged.get(key).unwrap_or(&0.0); + println!("{:<25}{}", systems::name_for(key), damage) } println!(); } diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 675ac8ec..515b7fb2 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -74,27 +74,47 @@ impl Enterprise { return } - let system = systems::ALL[rng.gen_range(0..systems::ALL.len())].to_string(); + let system = systems::KEYS[rng.gen_range(0..systems::KEYS.len())].to_string(); let damage = hit_past_shield + rng.gen::() * 0.5; - self.damaged.entry(system).and_modify(|d| *d -= damage).or_insert(-damage); + self.damage_system(&system, damage); + } + + pub fn damage_system(&mut self, system: &str, damage: f32) { + self.damaged.entry(system.to_string()).and_modify(|d| *d -= damage).or_insert(-damage); + } + + pub fn repair_system(&mut self, system: &str, amount: f32) -> bool { + let existing_damage = self.damaged[system]; + if existing_damage + amount >= 0.0 { + self.damaged.remove(system); + return true; + } + + self.damaged.entry(system.to_string()).and_modify(|d| *d += amount); + return false; } } pub mod systems { - use std::collections::HashMap; pub const SHORT_RANGE_SCAN: &str = "SRS"; pub const WARP_ENGINES: &str = "NAV"; pub const SHIELD_CONTROL: &str = "SHE"; pub const DAMAGE_CONTROL: &str = "DAM"; - pub const ALL: [&str; 4] = [ + pub const KEYS: [&str; 4] = [ SHORT_RANGE_SCAN, WARP_ENGINES, SHIELD_CONTROL, DAMAGE_CONTROL ]; - pub const NAMES: [&str; 4] = [ - "Short Range Scanners", "Warp Engines", "Shield Control", "Damage Control" - ]; + pub fn name_for(key: &str) -> String { + match key { + SHORT_RANGE_SCAN => "Short Range Scanners".into(), + WARP_ENGINES => "Warp Engines".into(), + SHIELD_CONTROL => "Shield Control".into(), + DAMAGE_CONTROL => "Damage Control".into(), + _ => "Unknown".into() + } + } } pub struct EndPosition { diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index e7bc5516..e34c44d4 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -208,3 +208,19 @@ pub fn damaged_engines(max_warp: f32, warp_factor: f32) { println!("Warp engines are damaged. Maximum speed = warp {max_warp} Chief Engineer Scott reports, 'The engines won't take warp {warp_factor} !'") } + +pub fn damage_control_report() { + println!("Damage Control report:") +} + +pub fn random_repair_report_for(name: &str, damaged: bool) { + let mut message = "state of repair improved"; + if damaged { + message = "damaged"; + } + println!("Damage Control report: {name} {message}") +} + +pub fn system_repair_completed(name: String) { + println!(" {name} repair completed.") +} diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index e6c68ce1..0c81bce1 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -13,7 +13,7 @@ Started after movement and display of stats was finished (no energy management o - [x] shield hit absorption - [x] subsystem damage - [x] and support for reports -- [ ] random system damage on move +- [x] random system damage or repairs on move - [ ] lrs? - [ ] stranded... - [ ] stop before hitting an object From 4644a91024b2e0d5dfd70d68bf8b3cca61e01b66 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 18:56:47 +1300 Subject: [PATCH 060/141] implemented long range scanners --- 84_Super_Star_Trek/rust/src/commands.rs | 9 +++++++++ 84_Super_Star_Trek/rust/src/main.rs | 3 ++- 84_Super_Star_Trek/rust/src/model.rs | 6 ++++-- 84_Super_Star_Trek/rust/src/view.rs | 27 +++++++++++++++++++++++++ 84_Super_Star_Trek/rust/tasks.md | 3 +++ 5 files changed, 45 insertions(+), 3 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 3b14e4cf..25c2954d 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -206,3 +206,12 @@ pub fn display_damage_control(enterprise: &Enterprise) { } println!(); } + +pub fn perform_long_range_scan(galaxy: &Galaxy) { + if galaxy.enterprise.damaged.contains_key(model::systems::SHORT_RANGE_SCAN) { + view::inoperable("Long Range Scanners"); + return; + } + + view::long_range_scan(galaxy); +} \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 0809ec23..fd01b9b5 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -25,11 +25,12 @@ fn main() { if command.len() == 0 { continue; } - match command[0].to_uppercase().as_str() { + match command[0].to_uppercase().as_str() { // order is weird because i built it in this order :) model::systems::SHORT_RANGE_SCAN => commands::perform_short_range_scan(&galaxy), model::systems::WARP_ENGINES => commands::gather_dir_and_speed_then_move(&mut galaxy, command[1..].into()), model::systems::SHIELD_CONTROL => commands::get_amount_and_set_shields(&mut galaxy, command[1..].into()), model::systems::DAMAGE_CONTROL => commands::display_damage_control(&galaxy.enterprise), + model::systems::LONG_RANGE_SCAN => commands::perform_long_range_scan(&galaxy), _ => view::print_command_help() } diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 515b7fb2..f502a13f 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -101,9 +101,10 @@ pub mod systems { pub const WARP_ENGINES: &str = "NAV"; pub const SHIELD_CONTROL: &str = "SHE"; pub const DAMAGE_CONTROL: &str = "DAM"; + pub const LONG_RANGE_SCAN: &str = "LRS"; - pub const KEYS: [&str; 4] = [ - SHORT_RANGE_SCAN, WARP_ENGINES, SHIELD_CONTROL, DAMAGE_CONTROL + pub const KEYS: [&str; 5] = [ + SHORT_RANGE_SCAN, WARP_ENGINES, SHIELD_CONTROL, DAMAGE_CONTROL, LONG_RANGE_SCAN ]; pub fn name_for(key: &str) -> String { @@ -112,6 +113,7 @@ pub mod systems { WARP_ENGINES => "Warp Engines".into(), SHIELD_CONTROL => "Shield Control".into(), DAMAGE_CONTROL => "Damage Control".into(), + LONG_RANGE_SCAN => "Long Range Scanners".into(), _ => "Unknown".into() } } diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index e34c44d4..26daf070 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -224,3 +224,30 @@ pub fn random_repair_report_for(name: &str, damaged: bool) { pub fn system_repair_completed(name: String) { println!(" {name} repair completed.") } + +pub fn long_range_scan(galaxy: &Galaxy) { + + let cx = galaxy.enterprise.quadrant.0 as i8; + let cy = galaxy.enterprise.quadrant.1 as i8; + + println!("Long range scan for quadrant {}", galaxy.enterprise.quadrant); + println!("{:-^19}", ""); + for y in cy - 1..=cy + 1 { + for x in cx - 1..=cx + 1 { + let mut klingons = "*".into(); + let mut star_bases = "*".into(); + let mut stars = "*".into(); + + if y >= 0 && y < 8 && x >= 0 && x < 8 { + let quadrant = &galaxy.quadrants[Pos(x as u8, y as u8).as_index()]; + klingons = format!("{}", quadrant.klingons.len()); + star_bases = quadrant.star_base.map_or("0", |_| "1"); + stars = format!("{}", quadrant.stars.len()); + } + + print!(": {}{}{} ", klingons, stars, star_bases) + } + println!(":"); + println!("{:-^19}", ""); + } +} diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 0c81bce1..8dd2d166 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -19,6 +19,7 @@ Started after movement and display of stats was finished (no energy management o - [ ] stop before hitting an object - when moving across a sector, the enterprise should stop before it runs into something - the current move is a jump, which makes this problematic. would need to rewrite it + - also, movement courses could be floats, according to the instructions, allowing for more precise movement and aiming - [x] better command reading - support entering multiple values on a line (e.g. nav 3 0.1) - [ ] starbases - [ ] repair @@ -26,3 +27,5 @@ Started after movement and display of stats was finished (no energy management o - [ ] phasers - [ ] torpedoes - [ ] restarting the game +- [ ] time progression + - check all areas where time should move, and adjust accordingly From 460dcd7ab44ca6c3df435b33a43e698f17335c3b Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 18:57:53 +1300 Subject: [PATCH 061/141] slight tweak to allow close to fixed to be fixed --- 84_Super_Star_Trek/rust/src/model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index f502a13f..a55eb62f 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -85,7 +85,7 @@ impl Enterprise { pub fn repair_system(&mut self, system: &str, amount: f32) -> bool { let existing_damage = self.damaged[system]; - if existing_damage + amount >= 0.0 { + if existing_damage + amount >= -0.1 { self.damaged.remove(system); return true; } From d76b0482369cff2c82a83ac9804d78f3aaad6835 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 19:01:48 +1300 Subject: [PATCH 062/141] revised random repair or damage to only repair damaged systems --- 84_Super_Star_Trek/rust/src/commands.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 25c2954d..099b5496 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -103,16 +103,25 @@ fn repair_or_damage_random_system(enterprise: &mut Enterprise) { return; } + if rng.gen::() >= 0.6 { + if enterprise.damaged.len() == 0 { + return; + } + + let damaged: Vec = enterprise.damaged.keys().map(|k| k.to_string()).collect(); + let system = damaged[rng.gen_range(0..damaged.len())].to_string(); + let system_name = &systems::name_for(&system); + + enterprise.repair_system(&system, rng.gen::() * 3.0 + 1.0); + view::random_repair_report_for(system_name, false); + return; + } + let system = systems::KEYS[rng.gen_range(0..systems::KEYS.len())].to_string(); let system_name = &systems::name_for(&system); - if rng.gen::() >= 0.6 { - enterprise.repair_system(&system, rng.gen::() * 3.0 + 1.0); - view::random_repair_report_for(system_name, false); - } else { - enterprise.damage_system(&system, rng.gen::() * 5.0 + 1.0); - view::random_repair_report_for(system_name, true); - } + enterprise.damage_system(&system, rng.gen::() * 5.0 + 1.0); + view::random_repair_report_for(system_name, true); } fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { From 2360bfd0c2dd869025a4ff46490852178f785ac7 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 19:08:21 +1300 Subject: [PATCH 063/141] implemented getting stranded --- 84_Super_Star_Trek/rust/src/main.rs | 2 +- 84_Super_Star_Trek/rust/src/model.rs | 8 ++++++++ 84_Super_Star_Trek/rust/src/view.rs | 6 ++++++ 84_Super_Star_Trek/rust/tasks.md | 5 +++-- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index fd01b9b5..385d122c 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -34,7 +34,7 @@ fn main() { _ => view::print_command_help() } - if galaxy.enterprise.destroyed { // todo: also check if stranded + if galaxy.enterprise.destroyed || galaxy.enterprise.check_stranded() { view::end_game_failure(&galaxy); // todo check if can restart break; diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index a55eb62f..f231124c 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -93,6 +93,14 @@ impl Enterprise { self.damaged.entry(system.to_string()).and_modify(|d| *d += amount); return false; } + + pub fn check_stranded(&self) -> bool { + if self.total_energy < 10 || (self.total_energy - self.shields < 10 && self.damaged.contains_key(systems::SHIELD_CONTROL)) { + view::stranded(); + return true; + } + return false; + } } pub mod systems { diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 26daf070..fc4a4735 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -251,3 +251,9 @@ pub fn long_range_scan(galaxy: &Galaxy) { println!("{:-^19}", ""); } } + +pub fn stranded() { + println!("** FATAL ERROR ** You've just stranded your ship in space +You have insufficient maneuvering energy, and shield control +is presently incapable of cross-circuiting to engine room!!") +} diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 8dd2d166..a2729d9b 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -14,8 +14,8 @@ Started after movement and display of stats was finished (no energy management o - [x] subsystem damage - [x] and support for reports - [x] random system damage or repairs on move -- [ ] lrs? -- [ ] stranded... +- [x] lrs? +- [x] stranded... - [ ] stop before hitting an object - when moving across a sector, the enterprise should stop before it runs into something - the current move is a jump, which makes this problematic. would need to rewrite it @@ -29,3 +29,4 @@ Started after movement and display of stats was finished (no energy management o - [ ] restarting the game - [ ] time progression - check all areas where time should move, and adjust accordingly +- [ ] intro instructions From 134b17f77e1feb2bc5d1f56452605d69fc9b5490 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 19:59:57 +1300 Subject: [PATCH 064/141] implemented computer with region map --- 84_Super_Star_Trek/rust/src/commands.rs | 31 ++++- 84_Super_Star_Trek/rust/src/main.rs | 1 + 84_Super_Star_Trek/rust/src/model.rs | 6 +- 84_Super_Star_Trek/rust/src/view.rs | 166 ++++++++++++++++++++++-- 84_Super_Star_Trek/rust/tasks.md | 9 ++ 5 files changed, 194 insertions(+), 19 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 099b5496..db3d4a32 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -14,7 +14,7 @@ pub fn perform_short_range_scan(galaxy: &Galaxy) { pub fn get_amount_and_set_shields(galaxy: &mut Galaxy, provided: Vec) { if galaxy.enterprise.damaged.contains_key(model::systems::SHIELD_CONTROL) { - view::inoperable("Shield Control"); + view::inoperable(&systems::name_for(systems::SHIELD_CONTROL)); return; } @@ -204,7 +204,7 @@ fn move_klingons_and_fire(galaxy: &mut Galaxy) { pub fn display_damage_control(enterprise: &Enterprise) { if enterprise.damaged.contains_key(model::systems::DAMAGE_CONTROL) { - view::inoperable("Damage Control"); + view::inoperable(&systems::name_for(systems::DAMAGE_CONTROL)); return; } @@ -217,10 +217,33 @@ pub fn display_damage_control(enterprise: &Enterprise) { } pub fn perform_long_range_scan(galaxy: &Galaxy) { - if galaxy.enterprise.damaged.contains_key(model::systems::SHORT_RANGE_SCAN) { - view::inoperable("Long Range Scanners"); + if galaxy.enterprise.damaged.contains_key(model::systems::LONG_RANGE_SCAN) { + view::inoperable(&systems::name_for(systems::LONG_RANGE_SCAN)); return; } view::long_range_scan(galaxy); +} + +pub fn access_computer(galaxy: &Galaxy, provided: Vec) { + if galaxy.enterprise.damaged.contains_key(model::systems::COMPUTER) { + view::inoperable(&systems::name_for(systems::COMPUTER)); + return; + } + + let operation : i32; + loop { + let entered = input::param_or_prompt_value(&provided, 0, "Computer active and waiting command?", 0, 5); + if entered.is_none() { + view::computer_options(); + } else { + operation = entered.unwrap(); + break; + } + } + + match operation { + 5 => view::galaxy_region_map(), + _ => todo!() // todo implement others + } } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 385d122c..f7db0845 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -31,6 +31,7 @@ fn main() { model::systems::SHIELD_CONTROL => commands::get_amount_and_set_shields(&mut galaxy, command[1..].into()), model::systems::DAMAGE_CONTROL => commands::display_damage_control(&galaxy.enterprise), model::systems::LONG_RANGE_SCAN => commands::perform_long_range_scan(&galaxy), + model::systems::COMPUTER => commands::access_computer(&galaxy, command[1..].into()), _ => view::print_command_help() } diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index f231124c..549031db 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -110,9 +110,10 @@ pub mod systems { pub const SHIELD_CONTROL: &str = "SHE"; pub const DAMAGE_CONTROL: &str = "DAM"; pub const LONG_RANGE_SCAN: &str = "LRS"; + pub const COMPUTER: &str = "COM"; - pub const KEYS: [&str; 5] = [ - SHORT_RANGE_SCAN, WARP_ENGINES, SHIELD_CONTROL, DAMAGE_CONTROL, LONG_RANGE_SCAN + pub const KEYS: [&str; 6] = [ + SHORT_RANGE_SCAN, WARP_ENGINES, SHIELD_CONTROL, DAMAGE_CONTROL, LONG_RANGE_SCAN, COMPUTER ]; pub fn name_for(key: &str) -> String { @@ -122,6 +123,7 @@ pub mod systems { SHIELD_CONTROL => "Shield Control".into(), DAMAGE_CONTROL => "Damage Control".into(), LONG_RANGE_SCAN => "Long Range Scanners".into(), + COMPUTER => "Library-Computer".into(), _ => "Unknown".into() } } diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index fc4a4735..e4fbed20 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -1,5 +1,114 @@ use crate::model::{Galaxy, Pos, EndPosition, SectorStatus}; +pub fn full_instructions() { + println!( +" INSTRUCTIONS FOR 'SUPER STAR TREK' + + 1. When you see \"Command ?\" printed, enter one of the legal + commands (NAV, SRS, LRS, PHA, TOR, SHE, DAM, COM, OR XXX). + 2. If you should type in an illegal command, you'll get a short + list of the legal commands printed out. + 3. Some commands require you to enter data (for example, the + 'NAV' command comes back with 'Course (1-9) ?'.) If you + type in illegal data (like negative numbers), then command + will be aborted. + + The galaxy is divided into an 8 X 8 quadrant grid, + and each quadrant is further divided into an 8 X 8 sector grid. + + You will be assigned a starting point somewhere in the + galaxy to begin a tour of duty as commander of the starship + Enterprise; your mission: to seek and destroy the fleet of + Klingon warships which are menacing the United Federation of + Planets. + + You have the following commands available to you as captain + of the starship Enterprise: + + NAV command = Warp Engine Control + Course is in a circular numerical 4 3 2 + vector arrangement as shown . . . + integer and real values may be ... + used. (Thus course 1.5 is half- 5 ---*--- 1 + way between 1 and 2. ... + . . . + Values may approach 9.0, which 6 7 8 + itself is equivalent to 1.0 + COURSE + One warp factor is the size of + one quadrant. Therefore, to get + from quadrant 6,5 to 5,5, you WOULD + use course 3, warp factor 1. + + SRS command = Short Range Sensor Scan + Shows you a scan of your present quadrant. + + Symbology on your sensor screen is as follows: + <*> = Your starship's position + +K+ = Klingon battle cruiser + >!< = Federation starbase (refuel/repair/re-arm here!) + * = Star + + A condensed 'status report' will also be presented. + + LRS command = Long Range Sensor Scan + Shows conditions in space for one quadrant on each side + of the Enterprise (which is in the middle of the scan). + The scan is coded in the form ###, where the units digit + is the number of stars, the tens digit is the number of + starbases, and the hundreds digit is the number of + Klingons. + + Example - 207 = 2 Klingons, No starbases, & 7 stars. + + PHA command = Phaser Control + Allows you to destroy the Klingon battle cruisers by + zapping them with suitably large units of energy to + deplete their shield power. (Remember, Klingons have + phasers, too!) + + TOR command = Photon Torpedo Control + Torpedo course is the same as used in warp engine control. + If you hit the Klingon vessel, he is destroyed and + cannot fire back at you. If you miss, you are subject to + his phaser fire. In either case, you are also subject to + the phaser fire of all other Klingons in the quadrant. + + The library-computer (COM command) has an option to + compute torpedo trajectory for you (Option 2). + + SHE command = Shield Control + Defines the number of energy units to be assigned to the + shields. Energy is taken from total ship's energy. Note + that the status display total energy includes shield energy. + + DAM command = Damage Control Report + Gives the state of repair of all devices. Where a negative + 'state of repair' shows that the device is temporarily + damaged. + + COM command = Library-Computer + The library-computer contains six options: + Option 0 = Cumulative Galactic Record + This option shows computer memory of the results of all + previous short and long range sensor scans. + Option 1 = Status Report + This option shows the number of Klingons, Stardates, + and starbases remaining in the game. + Option 2 = Photon Torpedo Data + Which gives directions and distance from the Enterprise + to all Klingons in your quadrant. + Option 3 = Starbase Nav Data + This option gives direction and distance to any + starbase within your quadrant. + Option 4 = Direction/Distance Calculator + This option allows you to enter coordinates for + direction/distance calculations. + Option 5 = Galactic Region Name Map + This option prints the names of the sixteen major + galactic regions referred to in the game.") +} + pub fn enterprise() { println!(" @@ -34,10 +143,11 @@ pub fn intro(model: &Galaxy) { if star_bases > 1 { star_base_message = format!("There are {} starbases", star_bases); } - println!("Your orders are as follows: + println!( +"Your orders are as follows: Destroy the {} Klingon warships which have invaded - the galaxy before they can attack federation headquarters - on stardate {}. This gives you {} days. {} in the galaxy for resupplying your ship.\n", + the galaxy before they can attack federation headquarters + on stardate {}. This gives you {} days. {} in the galaxy for resupplying your ship.\n", model.remaining_klingons(), model.final_stardate, model.final_stardate - model.stardate, star_base_message) } @@ -69,7 +179,8 @@ fn quadrant_name(quadrant: &Pos) -> String { } pub fn starting_quadrant(quadrant: &Pos) { - println!("\nYour mission begins with your starship located + println!( +"\nYour mission begins with your starship located in the galactic quadrant, '{}'.\n", quadrant_name(quadrant)) } @@ -118,7 +229,8 @@ pub fn short_range_scan(model: &Galaxy) { } pub fn print_command_help() { - println!("Enter one of the following: + println!( +"Enter one of the following: NAV (To set course) SRS (For short range sensor scan) LRS (For long range sensor scan) @@ -132,7 +244,8 @@ pub fn print_command_help() { } pub fn end_game_failure(galaxy: &Galaxy) { - println!("Is is stardate {}. + println!( +"Is is stardate {}. There were {} Klingon battle cruisers left at the end of your mission. ", galaxy.stardate, galaxy.remaining_klingons()); @@ -151,10 +264,11 @@ pub fn enterprise_hit(hit_strength: &u16, from_sector: &Pos) { } pub fn hit_edge(end: &EndPosition) { - println!("Lt. Uhura report message from Starfleet Command: - 'Permission to attempt crossing of galactic perimeter + println!( +"Lt. Uhura report message from Starfleet Command: + 'Permission to attempt crossing of galactic perimeter is hereby *Denied*. Shut down your engines.' - Chief Engineer Scott reports, 'Warp engines shut down + Chief Engineer Scott reports, 'Warp engines shut down at sector {} of quadrant {}.'", end.quadrant, end.sector); } @@ -167,7 +281,8 @@ pub fn danger_shields() { } pub fn insuffient_warp_energy(warp_speed: f32) { - println!("Engineering reports, 'Insufficient energy available + println!( +"Engineering reports, 'Insufficient energy available for maneuvering at warp {warp_speed} !'") } @@ -188,7 +303,8 @@ pub fn ridiculous() { } pub fn shields_set(value: u16) { - println!("Deflector control room report: + println!( +"Deflector control room report: 'Shields now at {value} units per your command.'") } @@ -205,7 +321,8 @@ pub fn scanners_out() { } pub fn damaged_engines(max_warp: f32, warp_factor: f32) { - println!("Warp engines are damaged. Maximum speed = warp {max_warp} + println!( +"Warp engines are damaged. Maximum speed = warp {max_warp} Chief Engineer Scott reports, 'The engines won't take warp {warp_factor} !'") } @@ -253,7 +370,30 @@ pub fn long_range_scan(galaxy: &Galaxy) { } pub fn stranded() { - println!("** FATAL ERROR ** You've just stranded your ship in space + println!( +"** FATAL ERROR ** You've just stranded your ship in space You have insufficient maneuvering energy, and shield control is presently incapable of cross-circuiting to engine room!!") } + +pub fn computer_options() { + println!( +" 0 = Cumulative galactic record + 1 = Status report + 2 = Photon torpedo data + 3 = Starbase nav data + 4 = Direction/distance calculator + 5 = Galaxy 'region name' map") +} + +pub fn galaxy_region_map() { + println!( +" The Galaxy + 1 2 3 4 5 6 7 8 + ----- ----- ----- ----- ----- ----- ----- -----"); + for i in (0..REGION_NAMES.len()-1).step_by(2) { + println!( +"{} {:^23} {:^23} + ----- ----- ----- ----- ----- ----- ----- -----", (i/2)+1, REGION_NAMES[i], REGION_NAMES[i+1]); + } +} diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index a2729d9b..46ba7d6a 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -26,7 +26,16 @@ Started after movement and display of stats was finished (no energy management o - [ ] weapons - [ ] phasers - [ ] torpedoes +- [ ] computer + - [ ] 0 - output of all short and long range scans (requires tracking if a system has been scanned) + - [ ] 1 - klingons, starbases, stardate and damage control + - [ ] 2 - photon torpedo data: direction and distance to all local klingons + - [ ] 3 - starbase distance and dir locally + - [ ] 4 - direction/distance calculator (useful for nav actions I guess) + - [x] 5 - galactic name map + - [ ] restarting the game - [ ] time progression - check all areas where time should move, and adjust accordingly - [ ] intro instructions +- [ ] victory From 765fa239018778d22995f185845dfe82100680c3 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 20:29:08 +1300 Subject: [PATCH 065/141] implemented scan history --- 84_Super_Star_Trek/rust/src/commands.rs | 9 +++++-- 84_Super_Star_Trek/rust/src/main.rs | 2 +- 84_Super_Star_Trek/rust/src/model.rs | 9 +++++-- 84_Super_Star_Trek/rust/src/view.rs | 34 ++++++++++++++++++++++--- 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index db3d4a32..20e694e0 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -143,6 +143,7 @@ fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { if ship.quadrant != end.quadrant { view::enter_quadrant(&end.quadrant); + galaxy.scanned.insert(end.quadrant); if galaxy.quadrants[end.quadrant.as_index()].klingons.len() > 0 { view::condition_red(); @@ -216,13 +217,16 @@ pub fn display_damage_control(enterprise: &Enterprise) { println!(); } -pub fn perform_long_range_scan(galaxy: &Galaxy) { +pub fn perform_long_range_scan(galaxy: &mut Galaxy) { if galaxy.enterprise.damaged.contains_key(model::systems::LONG_RANGE_SCAN) { view::inoperable(&systems::name_for(systems::LONG_RANGE_SCAN)); return; } - view::long_range_scan(galaxy); + let seen = view::long_range_scan(galaxy); + for pos in seen { + galaxy.scanned.insert(pos); + } } pub fn access_computer(galaxy: &Galaxy, provided: Vec) { @@ -243,6 +247,7 @@ pub fn access_computer(galaxy: &Galaxy, provided: Vec) { } match operation { + 0 => view::galaxy_scanned_map(galaxy), 5 => view::galaxy_region_map(), _ => todo!() // todo implement others } diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index f7db0845..4b809b97 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -30,7 +30,7 @@ fn main() { model::systems::WARP_ENGINES => commands::gather_dir_and_speed_then_move(&mut galaxy, command[1..].into()), model::systems::SHIELD_CONTROL => commands::get_amount_and_set_shields(&mut galaxy, command[1..].into()), model::systems::DAMAGE_CONTROL => commands::display_damage_control(&galaxy.enterprise), - model::systems::LONG_RANGE_SCAN => commands::perform_long_range_scan(&galaxy), + model::systems::LONG_RANGE_SCAN => commands::perform_long_range_scan(&mut galaxy), model::systems::COMPUTER => commands::access_computer(&galaxy, command[1..].into()), _ => view::print_command_help() } diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 549031db..9bb386f1 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -1,4 +1,4 @@ -use std::{ops::{Mul, Add}, fmt::Display, collections::HashMap}; +use std::{ops::{Mul, Add}, fmt::Display, collections::{HashMap, HashSet}}; use rand::Rng; @@ -8,6 +8,7 @@ pub struct Galaxy { pub stardate: f32, pub final_stardate: f32, pub quadrants: Vec, + pub scanned: HashSet, pub enterprise: Enterprise } @@ -136,7 +137,7 @@ pub struct EndPosition { pub energy_cost: u16, } -#[derive(PartialEq, Clone, Copy, Debug)] +#[derive(PartialEq, Clone, Copy, Debug, Hash, Eq)] pub struct Pos(pub u8, pub u8); impl Pos { @@ -206,10 +207,14 @@ impl Galaxy { let enterprise_sector = quadrants[enterprise_quadrant.as_index()].find_empty_sector(); let stardate = rng.gen_range(20..=40) as f32 * 100.0; + let mut scanned = HashSet::new(); + scanned.insert(enterprise_quadrant); + Galaxy { stardate, final_stardate: stardate + rng.gen_range(25..=35) as f32, quadrants: quadrants, + scanned: scanned, enterprise: Enterprise { destroyed: false, damaged: HashMap::new(), diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index e4fbed20..fec5434c 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -174,7 +174,7 @@ const SUB_REGION_NAMES: [&str; 4] = ["I", "II", "III", "IV"]; fn quadrant_name(quadrant: &Pos) -> String { format!("{} {}", - REGION_NAMES[((quadrant.0 << 1) + (quadrant.1 >> 2)) as usize], + REGION_NAMES[((quadrant.1 << 1) + (quadrant.0 >> 2)) as usize], SUB_REGION_NAMES[(quadrant.1 % 4) as usize]) } @@ -342,11 +342,13 @@ pub fn system_repair_completed(name: String) { println!(" {name} repair completed.") } -pub fn long_range_scan(galaxy: &Galaxy) { +pub fn long_range_scan(galaxy: &Galaxy) -> Vec { let cx = galaxy.enterprise.quadrant.0 as i8; let cy = galaxy.enterprise.quadrant.1 as i8; + let mut seen = Vec::new(); + println!("Long range scan for quadrant {}", galaxy.enterprise.quadrant); println!("{:-^19}", ""); for y in cy - 1..=cy + 1 { @@ -356,7 +358,10 @@ pub fn long_range_scan(galaxy: &Galaxy) { let mut stars = "*".into(); if y >= 0 && y < 8 && x >= 0 && x < 8 { - let quadrant = &galaxy.quadrants[Pos(x as u8, y as u8).as_index()]; + let pos = Pos(x as u8, y as u8); + seen.push(pos); + + let quadrant = &galaxy.quadrants[pos.as_index()]; klingons = format!("{}", quadrant.klingons.len()); star_bases = quadrant.star_base.map_or("0", |_| "1"); stars = format!("{}", quadrant.stars.len()); @@ -367,6 +372,8 @@ pub fn long_range_scan(galaxy: &Galaxy) { println!(":"); println!("{:-^19}", ""); } + + seen } pub fn stranded() { @@ -397,3 +404,24 @@ pub fn galaxy_region_map() { ----- ----- ----- ----- ----- ----- ----- -----", (i/2)+1, REGION_NAMES[i], REGION_NAMES[i+1]); } } + +pub(crate) fn galaxy_scanned_map(galaxy: &Galaxy) { + println!( +"Computer record of galaxy for quadrant {} + 1 2 3 4 5 6 7 8 + ----- ----- ----- ----- ----- ----- ----- -----", galaxy.enterprise.quadrant); + for y in 0..8 { + print!("{} ", y+1); + for x in 0..8 { + let pos = Pos(x, y); + if galaxy.scanned.contains(&pos) { + let quadrant = &galaxy.quadrants[pos.as_index()]; + print!(" {}{}{} ", quadrant.klingons.len(), quadrant.stars.len(), quadrant.star_base.map_or("0", |_| "1")) + } else { + print!(" *** "); + } + } + println!( +"\n ----- ----- ----- ----- ----- ----- ----- -----") + } +} From 873b974473d94dfdc7941f8e5baacbd95d189ddd Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 21:34:04 +1300 Subject: [PATCH 066/141] work on phaser control everything but destruction of klingons --- 84_Super_Star_Trek/rust/src/commands.rs | 62 ++++++++++++++++++++++--- 84_Super_Star_Trek/rust/src/main.rs | 15 +++--- 84_Super_Star_Trek/rust/src/model.rs | 8 +++- 84_Super_Star_Trek/rust/src/view.rs | 14 ++++++ 84_Super_Star_Trek/rust/tasks.md | 6 ++- 5 files changed, 87 insertions(+), 18 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 20e694e0..52ca5daf 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -1,9 +1,9 @@ use rand::Rng; -use crate::{model::{Galaxy, Pos, COURSES, EndPosition, self, Enterprise, systems}, view, input}; +use crate::{model::{Galaxy, Pos, COURSES, EndPosition, self, Enterprise, systems}, view, input::{self, prompt_value, param_or_prompt_value}}; pub fn perform_short_range_scan(galaxy: &Galaxy) { - if galaxy.enterprise.damaged.contains_key(model::systems::SHORT_RANGE_SCAN) { + if galaxy.enterprise.damaged.contains_key(systems::SHORT_RANGE_SCAN) { view::scanners_out(); return; } @@ -13,7 +13,7 @@ pub fn perform_short_range_scan(galaxy: &Galaxy) { pub fn get_amount_and_set_shields(galaxy: &mut Galaxy, provided: Vec) { - if galaxy.enterprise.damaged.contains_key(model::systems::SHIELD_CONTROL) { + if galaxy.enterprise.damaged.contains_key(systems::SHIELD_CONTROL) { view::inoperable(&systems::name_for(systems::SHIELD_CONTROL)); return; } @@ -47,7 +47,7 @@ pub fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy, provided: Vec let course = course.unwrap(); let mut max_warp = 8.0; - if galaxy.enterprise.damaged.contains_key(model::systems::WARP_ENGINES) { + if galaxy.enterprise.damaged.contains_key(systems::WARP_ENGINES) { max_warp = 0.2; } @@ -204,7 +204,7 @@ fn move_klingons_and_fire(galaxy: &mut Galaxy) { } pub fn display_damage_control(enterprise: &Enterprise) { - if enterprise.damaged.contains_key(model::systems::DAMAGE_CONTROL) { + if enterprise.damaged.contains_key(systems::DAMAGE_CONTROL) { view::inoperable(&systems::name_for(systems::DAMAGE_CONTROL)); return; } @@ -218,7 +218,7 @@ pub fn display_damage_control(enterprise: &Enterprise) { } pub fn perform_long_range_scan(galaxy: &mut Galaxy) { - if galaxy.enterprise.damaged.contains_key(model::systems::LONG_RANGE_SCAN) { + if galaxy.enterprise.damaged.contains_key(systems::LONG_RANGE_SCAN) { view::inoperable(&systems::name_for(systems::LONG_RANGE_SCAN)); return; } @@ -230,7 +230,7 @@ pub fn perform_long_range_scan(galaxy: &mut Galaxy) { } pub fn access_computer(galaxy: &Galaxy, provided: Vec) { - if galaxy.enterprise.damaged.contains_key(model::systems::COMPUTER) { + if galaxy.enterprise.damaged.contains_key(systems::COMPUTER) { view::inoperable(&systems::name_for(systems::COMPUTER)); return; } @@ -251,4 +251,52 @@ pub fn access_computer(galaxy: &Galaxy, provided: Vec) { 5 => view::galaxy_region_map(), _ => todo!() // todo implement others } +} + +pub fn get_power_and_fire_phasers(galaxy: &mut Galaxy, provided: Vec) { + if galaxy.enterprise.damaged.contains_key(systems::PHASERS) { + view::inoperable(&systems::name_for(systems::PHASERS)); + return; + } + + let quadrant = &mut galaxy.quadrants[galaxy.enterprise.quadrant.as_index()]; + if quadrant.klingons.len() == 0 { + view::no_local_enemies(); + return; + } + + let computer_damaged = galaxy.enterprise.damaged.contains_key(systems::COMPUTER); + if computer_damaged { + view::computer_accuracy_issue(); + } + + let available_energy = galaxy.enterprise.total_energy - galaxy.enterprise.shields; + view::phasers_locked(available_energy); + let mut power: f32; + loop { + let setting = param_or_prompt_value(&provided, 0, "Number of units to fire", 0, available_energy); + if setting.is_some() { + power = setting.unwrap() as f32; + break; + } + } + + if power == 0.0 { + return; + } + + galaxy.enterprise.total_energy -= power as u16; + + let mut rng = rand::thread_rng(); + if computer_damaged { + power *= rng.gen::(); + } + + let per_enemy = power / quadrant.klingons.len() as f32; + + // fire on each klingon + + for klingon in &mut quadrant.klingons { + klingon.fire_on(&mut galaxy.enterprise) + } } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 4b809b97..1bd1d652 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -1,6 +1,6 @@ use std::process::exit; -use model::Galaxy; +use model::{Galaxy, systems}; mod input; mod model; @@ -26,12 +26,13 @@ fn main() { continue; } match command[0].to_uppercase().as_str() { // order is weird because i built it in this order :) - model::systems::SHORT_RANGE_SCAN => commands::perform_short_range_scan(&galaxy), - model::systems::WARP_ENGINES => commands::gather_dir_and_speed_then_move(&mut galaxy, command[1..].into()), - model::systems::SHIELD_CONTROL => commands::get_amount_and_set_shields(&mut galaxy, command[1..].into()), - model::systems::DAMAGE_CONTROL => commands::display_damage_control(&galaxy.enterprise), - model::systems::LONG_RANGE_SCAN => commands::perform_long_range_scan(&mut galaxy), - model::systems::COMPUTER => commands::access_computer(&galaxy, command[1..].into()), + systems::SHORT_RANGE_SCAN => commands::perform_short_range_scan(&galaxy), + systems::WARP_ENGINES => commands::gather_dir_and_speed_then_move(&mut galaxy, command[1..].into()), + systems::SHIELD_CONTROL => commands::get_amount_and_set_shields(&mut galaxy, command[1..].into()), + systems::DAMAGE_CONTROL => commands::display_damage_control(&galaxy.enterprise), + systems::LONG_RANGE_SCAN => commands::perform_long_range_scan(&mut galaxy), + systems::COMPUTER => commands::access_computer(&galaxy, command[1..].into()), + systems::PHASERS => commands::get_power_and_fire_phasers(&mut galaxy, command[1..].into()), _ => view::print_command_help() } diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 9bb386f1..dbd23522 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -25,6 +25,8 @@ pub struct Klingon { impl Klingon { pub fn fire_on(&mut self, enterprise: &mut Enterprise) { + // todo check if enterprise is protected + let mut rng = rand::thread_rng(); let attack_strength = rng.gen::(); let dist_to_enterprise = self.sector.abs_diff(enterprise.sector) as f32; @@ -112,9 +114,10 @@ pub mod systems { pub const DAMAGE_CONTROL: &str = "DAM"; pub const LONG_RANGE_SCAN: &str = "LRS"; pub const COMPUTER: &str = "COM"; + pub const PHASERS: &str = "PHA"; - pub const KEYS: [&str; 6] = [ - SHORT_RANGE_SCAN, WARP_ENGINES, SHIELD_CONTROL, DAMAGE_CONTROL, LONG_RANGE_SCAN, COMPUTER + pub const KEYS: [&str; 7] = [ + SHORT_RANGE_SCAN, WARP_ENGINES, SHIELD_CONTROL, DAMAGE_CONTROL, LONG_RANGE_SCAN, COMPUTER, PHASERS ]; pub fn name_for(key: &str) -> String { @@ -125,6 +128,7 @@ pub mod systems { DAMAGE_CONTROL => "Damage Control".into(), LONG_RANGE_SCAN => "Long Range Scanners".into(), COMPUTER => "Library-Computer".into(), + PHASERS => "Phaser Control".into(), _ => "Unknown".into() } } diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index fec5434c..1c904958 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -425,3 +425,17 @@ pub(crate) fn galaxy_scanned_map(galaxy: &Galaxy) { "\n ----- ----- ----- ----- ----- ----- ----- -----") } } + +pub fn no_local_enemies() { + println!( +"Science Officer Spock reports, 'Sensors show no enemy ships + in this quadrant'") +} + +pub fn computer_accuracy_issue() { + println!("Computer failure hampers accuracy") +} + +pub fn phasers_locked(available_energy: u16) { + println!("Phasers locked on target; Energy available = {available_energy} units") +} diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 46ba7d6a..8f5b3bab 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -22,12 +22,14 @@ Started after movement and display of stats was finished (no energy management o - also, movement courses could be floats, according to the instructions, allowing for more precise movement and aiming - [x] better command reading - support entering multiple values on a line (e.g. nav 3 0.1) - [ ] starbases - - [ ] repair + - [ ] proximity detection for docking + - [ ] repair on damage control + - [ ] protection from shots - [ ] weapons - [ ] phasers - [ ] torpedoes - [ ] computer - - [ ] 0 - output of all short and long range scans (requires tracking if a system has been scanned) + - [x] 0 - output of all short and long range scans (requires tracking if a system has been scanned) - [ ] 1 - klingons, starbases, stardate and damage control - [ ] 2 - photon torpedo data: direction and distance to all local klingons - [ ] 3 - starbase distance and dir locally From 5973d97a16ed00a94e786a8e79557ed6125c6065 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 Mar 2023 21:35:45 +1300 Subject: [PATCH 067/141] some more reorg --- 84_Super_Star_Trek/rust/src/commands.rs | 7 +------ 84_Super_Star_Trek/rust/src/view.rs | 11 ++++++++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 52ca5daf..911f88f5 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -209,12 +209,7 @@ pub fn display_damage_control(enterprise: &Enterprise) { return; } - println!("Device State of Repair"); - for key in systems::KEYS { - let damage = enterprise.damaged.get(key).unwrap_or(&0.0); - println!("{:<25}{}", systems::name_for(key), damage) - } - println!(); + view::damage_control(enterprise); } pub fn perform_long_range_scan(galaxy: &mut Galaxy) { diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 1c904958..290fa37b 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -1,4 +1,4 @@ -use crate::model::{Galaxy, Pos, EndPosition, SectorStatus}; +use crate::model::{Galaxy, Pos, EndPosition, SectorStatus, Enterprise, systems}; pub fn full_instructions() { println!( @@ -342,6 +342,15 @@ pub fn system_repair_completed(name: String) { println!(" {name} repair completed.") } +pub fn damage_control(enterprise: &Enterprise) { + println!("Device State of Repair"); + for key in systems::KEYS { + let damage = enterprise.damaged.get(key).unwrap_or(&0.0); + println!("{:<25}{}", systems::name_for(key), damage) + } + println!(); +} + pub fn long_range_scan(galaxy: &Galaxy) -> Vec { let cx = galaxy.enterprise.quadrant.0 as i8; From 21ccbc0f9b8a7510300c054c62bc819cfa25833b Mon Sep 17 00:00:00 2001 From: Christopher Date: Fri, 3 Mar 2023 07:14:25 +1300 Subject: [PATCH 068/141] work on starbases --- 84_Super_Star_Trek/rust/src/commands.rs | 54 ++++++++++++++++++------- 84_Super_Star_Trek/rust/src/main.rs | 2 +- 84_Super_Star_Trek/rust/src/model.rs | 15 ++++--- 84_Super_Star_Trek/rust/src/view.rs | 9 ++++- 4 files changed, 59 insertions(+), 21 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 911f88f5..255ced43 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -1,6 +1,6 @@ use rand::Rng; -use crate::{model::{Galaxy, Pos, COURSES, EndPosition, self, Enterprise, systems}, view, input::{self, prompt_value, param_or_prompt_value}}; +use crate::{model::*, view, input::{self, param_or_prompt_value}}; pub fn perform_short_range_scan(galaxy: &Galaxy) { if galaxy.enterprise.damaged.contains_key(systems::SHORT_RANGE_SCAN) { @@ -64,7 +64,9 @@ pub fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy, provided: Vec return; } - move_klingons_and_fire(galaxy); + klingons_move(galaxy); + klingons_fire(galaxy); + if galaxy.enterprise.destroyed { return; } @@ -156,10 +158,17 @@ fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { ship.quadrant = end.quadrant; ship.sector = end.sector; - ship.total_energy = (ship.total_energy - end.energy_cost).max(0); - if ship.shields > ship.total_energy { - view::divert_energy_from_shields(); - ship.shields = ship.total_energy; + let quadrant = &galaxy.quadrants[end.quadrant.as_index()]; + if quadrant.docked_at_starbase(ship.sector) { + ship.shields = 0; + ship.photon_torpedoes = MAX_PHOTON_TORPEDOES; + ship.total_energy = MAX_ENERGY; + } else { + ship.total_energy = (ship.total_energy - end.energy_cost).max(0); + if ship.shields > ship.total_energy { + view::divert_energy_from_shields(); + ship.shields = ship.total_energy; + } } view::short_range_scan(&galaxy) @@ -189,27 +198,46 @@ fn find_end_quadrant_sector(start_quadrant: Pos, start_sector: Pos, course: u8, EndPosition { quadrant, sector, hit_edge, energy_cost } } -fn move_klingons_and_fire(galaxy: &mut Galaxy) { +fn klingons_move(galaxy: &mut Galaxy) { let quadrant = &mut galaxy.quadrants[galaxy.enterprise.quadrant.as_index()]; for k in 0..quadrant.klingons.len() { let new_sector = quadrant.find_empty_sector(); quadrant.klingons[k].sector = new_sector; } +} - // todo: check if enterprise is protected by a starbase +fn klingons_fire(galaxy: &mut Galaxy) { + let quadrant = &mut galaxy.quadrants[galaxy.enterprise.quadrant.as_index()]; + if quadrant.docked_at_starbase(galaxy.enterprise.sector) { + view::starbase_shields(); + return; + } for k in 0..quadrant.klingons.len() { quadrant.klingons[k].fire_on(&mut galaxy.enterprise); } } -pub fn display_damage_control(enterprise: &Enterprise) { - if enterprise.damaged.contains_key(systems::DAMAGE_CONTROL) { +pub fn run_damage_control(galaxy: &mut Galaxy) { + + let ship = &mut galaxy.enterprise; + + if ship.damaged.contains_key(systems::DAMAGE_CONTROL) { view::inoperable(&systems::name_for(systems::DAMAGE_CONTROL)); return; } - view::damage_control(enterprise); + view::damage_control(&ship); + + if ship.damaged.len() == 0 || !galaxy.quadrants[ship.quadrant.as_index()].docked_at_starbase(ship.sector) { + return; + } + + // try repeair + // if so write dam report + // and increment elapsed time + + view::damage_control(&ship); } pub fn perform_long_range_scan(galaxy: &mut Galaxy) { @@ -291,7 +319,5 @@ pub fn get_power_and_fire_phasers(galaxy: &mut Galaxy, provided: Vec) { // fire on each klingon - for klingon in &mut quadrant.klingons { - klingon.fire_on(&mut galaxy.enterprise) - } + klingons_fire(galaxy); } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 1bd1d652..589e43e0 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -29,7 +29,7 @@ fn main() { systems::SHORT_RANGE_SCAN => commands::perform_short_range_scan(&galaxy), systems::WARP_ENGINES => commands::gather_dir_and_speed_then_move(&mut galaxy, command[1..].into()), systems::SHIELD_CONTROL => commands::get_amount_and_set_shields(&mut galaxy, command[1..].into()), - systems::DAMAGE_CONTROL => commands::display_damage_control(&galaxy.enterprise), + systems::DAMAGE_CONTROL => commands::run_damage_control(&mut galaxy), systems::LONG_RANGE_SCAN => commands::perform_long_range_scan(&mut galaxy), systems::COMPUTER => commands::access_computer(&galaxy, command[1..].into()), systems::PHASERS => commands::get_power_and_fire_phasers(&mut galaxy, command[1..].into()), diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index dbd23522..31fb3191 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -25,8 +25,6 @@ pub struct Klingon { impl Klingon { pub fn fire_on(&mut self, enterprise: &mut Enterprise) { - // todo check if enterprise is protected - let mut rng = rand::thread_rng(); let attack_strength = rng.gen::(); let dist_to_enterprise = self.sector.abs_diff(enterprise.sector) as f32; @@ -149,7 +147,7 @@ impl Pos { (self.0 * 8 + self.1).into() } - fn abs_diff(&self, other: Pos) -> u8 { + pub fn abs_diff(&self, other: Pos) -> u8 { self.0.abs_diff(other.0) + self.1.abs_diff(other.1) } } @@ -192,6 +190,9 @@ pub enum SectorStatus { Empty, Star, StarBase, Klingon } +pub const MAX_PHOTON_TORPEDOES: u8 = 28; +pub const MAX_ENERGY: u16 = 3000; + impl Galaxy { pub fn remaining_klingons(&self) -> u8 { let quadrants = &self.quadrants; @@ -224,8 +225,8 @@ impl Galaxy { damaged: HashMap::new(), quadrant: enterprise_quadrant, sector: enterprise_sector, - photon_torpedoes: 28, - total_energy: 3000, + photon_torpedoes: MAX_PHOTON_TORPEDOES, + total_energy: MAX_ENERGY, shields: 0 } } } @@ -296,4 +297,8 @@ impl Quadrant { } } } + + pub fn docked_at_starbase(&self, enterprise_sector: Pos) -> bool { + self.star_base.is_some() && self.star_base.unwrap().abs_diff(enterprise_sector) == 1 + } } diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 290fa37b..c7cc836d 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -191,7 +191,10 @@ pub fn enter_quadrant(quadrant: &Pos) { pub fn short_range_scan(model: &Galaxy) { let quadrant = &model.quadrants[model.enterprise.quadrant.as_index()]; let mut condition = "GREEN"; - if quadrant.klingons.len() > 0 { + if quadrant.docked_at_starbase(model.enterprise.sector) { + println!("Shields dropped for docking purposes"); + condition = "DOCKED"; + } else if quadrant.klingons.len() > 0 { condition = "*RED*"; } else if model.enterprise.damaged.len() > 0 { condition = "YELLOW"; @@ -448,3 +451,7 @@ pub fn computer_accuracy_issue() { pub fn phasers_locked(available_energy: u16) { println!("Phasers locked on target; Energy available = {available_energy} units") } + +pub fn starbase_shields() { + println!("Starbase shields protect the Enterprise") +} From 5b58b37ad1f1caf95dfcfd6f236f945069a50244 Mon Sep 17 00:00:00 2001 From: Christopher Date: Fri, 3 Mar 2023 07:59:12 +1300 Subject: [PATCH 069/141] finished implementing starbases --- 84_Super_Star_Trek/rust/src/commands.rs | 15 +++++++++++---- 84_Super_Star_Trek/rust/src/input.rs | 16 ++++++++++++++++ 84_Super_Star_Trek/rust/src/model.rs | 21 +++++++++++++-------- 84_Super_Star_Trek/rust/src/view.rs | 12 +++++++++--- 84_Super_Star_Trek/rust/tasks.md | 8 ++++---- 5 files changed, 53 insertions(+), 19 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 255ced43..60dcb9b9 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -229,14 +229,21 @@ pub fn run_damage_control(galaxy: &mut Galaxy) { view::damage_control(&ship); - if ship.damaged.len() == 0 || !galaxy.quadrants[ship.quadrant.as_index()].docked_at_starbase(ship.sector) { + let quadrant = &galaxy.quadrants[ship.quadrant.as_index()]; + if ship.damaged.len() == 0 || !quadrant.docked_at_starbase(ship.sector) { return; } - // try repeair - // if so write dam report - // and increment elapsed time + let repair_delay = quadrant.star_base.as_ref().unwrap().repair_delay; + let repair_time = (ship.damaged.len() as f32 * 0.1 + repair_delay).max(0.9); + view::repair_estimate(repair_time); + if !input::prompt_yes_no("Will you authorize the repair order") { + return; + } + + ship.damaged.clear(); + galaxy.stardate += repair_time; view::damage_control(&ship); } diff --git a/84_Super_Star_Trek/rust/src/input.rs b/84_Super_Star_Trek/rust/src/input.rs index 75f12102..5bd35fb3 100644 --- a/84_Super_Star_Trek/rust/src/input.rs +++ b/84_Super_Star_Trek/rust/src/input.rs @@ -14,6 +14,22 @@ pub fn prompt(prompt_text: &str) -> Vec { Vec::new() } +pub fn prompt_yes_no(prompt_text: &str) -> bool { + loop { + let response = prompt(&format!("{prompt_text} (Y/N)")); + if response.len() == 0 { + continue; + } + let first_word = response[0].to_uppercase(); + if first_word.starts_with("Y") { + return true; + } + if first_word.starts_with("N") { + return false; + } + } +} + pub fn prompt_value(prompt_text: &str, min: T, max: T) -> Option { let passed = prompt(prompt_text); if passed.len() != 1 { diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 31fb3191..a12673af 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -14,10 +14,15 @@ pub struct Galaxy { pub struct Quadrant { pub stars: Vec, - pub star_base: Option, + pub star_base: Option, pub klingons: Vec } +pub struct StarBase { + pub sector: Pos, + pub repair_delay: f32, +} + pub struct Klingon { pub sector: Pos, energy: f32 @@ -243,7 +248,7 @@ impl Galaxy { } if rng.gen::() > 0.96 { - quadrant.star_base = Some(quadrant.find_empty_sector()); + quadrant.star_base = Some(StarBase { sector: quadrant.find_empty_sector(), repair_delay: rng.gen::() * 0.5 }); } let klingon_count = @@ -264,10 +269,10 @@ impl Galaxy { } impl Quadrant { - pub fn sector_status(&self, sector: &Pos) -> SectorStatus { + pub fn sector_status(&self, sector: Pos) -> SectorStatus { if self.stars.contains(§or) { SectorStatus::Star - } else if self.is_starbase(§or) { + } else if self.is_starbase(sector) { SectorStatus::StarBase } else if self.has_klingon(§or) { SectorStatus::Klingon @@ -276,10 +281,10 @@ impl Quadrant { } } - fn is_starbase(&self, sector: &Pos) -> bool { + fn is_starbase(&self, sector: Pos) -> bool { match &self.star_base { None => false, - Some(p) => p == sector + Some(p) => p.sector == sector } } @@ -292,13 +297,13 @@ impl Quadrant { let mut rng = rand::thread_rng(); loop { let pos = Pos(rng.gen_range(0..8), rng.gen_range(0..8)); - if self.sector_status(&pos) == SectorStatus::Empty { + if self.sector_status(pos) == SectorStatus::Empty { return pos } } } pub fn docked_at_starbase(&self, enterprise_sector: Pos) -> bool { - self.star_base.is_some() && self.star_base.unwrap().abs_diff(enterprise_sector) == 1 + self.star_base.is_some() && self.star_base.as_ref().unwrap().sector.abs_diff(enterprise_sector) == 1 } } diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index c7cc836d..77b70057 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -218,7 +218,7 @@ pub fn short_range_scan(model: &Galaxy) { if &pos == &model.enterprise.sector { print!("<*> ") } else { - match quadrant.sector_status(&pos) { + match quadrant.sector_status(pos) { SectorStatus::Star => print!(" * "), SectorStatus::StarBase => print!(">!< "), SectorStatus::Klingon => print!("+K+ "), @@ -375,7 +375,7 @@ pub fn long_range_scan(galaxy: &Galaxy) -> Vec { let quadrant = &galaxy.quadrants[pos.as_index()]; klingons = format!("{}", quadrant.klingons.len()); - star_bases = quadrant.star_base.map_or("0", |_| "1"); + star_bases = quadrant.star_base.as_ref().map_or("0", |_| "1"); stars = format!("{}", quadrant.stars.len()); } @@ -428,7 +428,7 @@ pub(crate) fn galaxy_scanned_map(galaxy: &Galaxy) { let pos = Pos(x, y); if galaxy.scanned.contains(&pos) { let quadrant = &galaxy.quadrants[pos.as_index()]; - print!(" {}{}{} ", quadrant.klingons.len(), quadrant.stars.len(), quadrant.star_base.map_or("0", |_| "1")) + print!(" {}{}{} ", quadrant.klingons.len(), quadrant.stars.len(), quadrant.star_base.as_ref().map_or("0", |_| "1")) } else { print!(" *** "); } @@ -455,3 +455,9 @@ pub fn phasers_locked(available_energy: u16) { pub fn starbase_shields() { println!("Starbase shields protect the Enterprise") } + +pub fn repair_estimate(repair_time: f32) { + println!( +"Technicians standing by to effect repairs to your ship; +Estimated time to repair: {repair_time} stardates.") +} diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 8f5b3bab..bdd44481 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -21,10 +21,10 @@ Started after movement and display of stats was finished (no energy management o - the current move is a jump, which makes this problematic. would need to rewrite it - also, movement courses could be floats, according to the instructions, allowing for more precise movement and aiming - [x] better command reading - support entering multiple values on a line (e.g. nav 3 0.1) -- [ ] starbases - - [ ] proximity detection for docking - - [ ] repair on damage control - - [ ] protection from shots +- [x] starbases + - [x] proximity detection for docking + - [x] repair on damage control + - [x] protection from shots - [ ] weapons - [ ] phasers - [ ] torpedoes From 188b17d1523db62761716dd8cddbd97bd62ca0d2 Mon Sep 17 00:00:00 2001 From: Christopher Date: Fri, 3 Mar 2023 08:04:32 +1300 Subject: [PATCH 070/141] small fix so klingons can't move on top of enterprise --- 84_Super_Star_Trek/rust/src/commands.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 60dcb9b9..3cc68cbc 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -201,7 +201,14 @@ fn find_end_quadrant_sector(start_quadrant: Pos, start_sector: Pos, course: u8, fn klingons_move(galaxy: &mut Galaxy) { let quadrant = &mut galaxy.quadrants[galaxy.enterprise.quadrant.as_index()]; for k in 0..quadrant.klingons.len() { - let new_sector = quadrant.find_empty_sector(); + let new_sector: Pos; + loop { + let candidate = quadrant.find_empty_sector(); + if candidate != galaxy.enterprise.sector { + new_sector = candidate; + break; + } + } quadrant.klingons[k].sector = new_sector; } } From a8c55988ade850b8294782d0603007f58752d016 Mon Sep 17 00:00:00 2001 From: Christopher Date: Fri, 3 Mar 2023 14:49:06 +1300 Subject: [PATCH 071/141] phasers! completed this --- 84_Super_Star_Trek/rust/src/commands.rs | 18 +++++++++++++++++- 84_Super_Star_Trek/rust/src/model.rs | 2 +- 84_Super_Star_Trek/rust/src/view.rs | 16 ++++++++++++++++ 84_Super_Star_Trek/rust/tasks.md | 2 +- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 3cc68cbc..6b2d4b08 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -331,7 +331,23 @@ pub fn get_power_and_fire_phasers(galaxy: &mut Galaxy, provided: Vec) { let per_enemy = power / quadrant.klingons.len() as f32; - // fire on each klingon + for k in &mut quadrant.klingons { + let dist = k.sector.abs_diff(galaxy.enterprise.sector) as f32; + let hit_strength = per_enemy / dist * (2.0 + rng.gen::()); + if hit_strength < 0.15 * k.energy { + view::no_damage(k.sector); + } else { + k.energy -= hit_strength; + view::hit_on_klingon(hit_strength, k.sector); + if k.energy > 0.0 { + view::klingon_remaining_energy(k.energy); + } else { + view::klingon_destroyed(); + } + } + } + + quadrant.klingons.retain(|k| k.energy > 0.0); klingons_fire(galaxy); } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index a12673af..151b85d3 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -25,7 +25,7 @@ pub struct StarBase { pub struct Klingon { pub sector: Pos, - energy: f32 + pub energy: f32 } impl Klingon { diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 77b70057..5372cc6b 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -461,3 +461,19 @@ pub fn repair_estimate(repair_time: f32) { "Technicians standing by to effect repairs to your ship; Estimated time to repair: {repair_time} stardates.") } + +pub fn no_damage(sector: Pos) { + println!("Sensors show no damage to enemy at {sector}") +} + +pub fn hit_on_klingon(hit_strength: f32, sector: Pos) { + println!("{hit_strength} unit hit on Klingon at sector {sector}") +} + +pub fn klingon_remaining_energy(energy: f32) { + println!(" (sensors show {energy} units remaining)") +} + +pub fn klingon_destroyed() { + println!(" Target Destroyed!") // not standard for game but feedback is good. Sorry Mr. Roddenberry +} diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index bdd44481..06bcf57a 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -26,7 +26,7 @@ Started after movement and display of stats was finished (no energy management o - [x] repair on damage control - [x] protection from shots - [ ] weapons - - [ ] phasers + - [x] phasers - [ ] torpedoes - [ ] computer - [x] 0 - output of all short and long range scans (requires tracking if a system has been scanned) From f7afb36cc7f67b651b34ba7f05b3f4dfdb399884 Mon Sep 17 00:00:00 2001 From: Christopher Date: Fri, 3 Mar 2023 16:16:01 +1300 Subject: [PATCH 072/141] work on victory and retry conditions --- 84_Super_Star_Trek/rust/src/main.rs | 20 +++++++++++++---- 84_Super_Star_Trek/rust/src/model.rs | 33 +++++++++++++++++----------- 84_Super_Star_Trek/rust/src/view.rs | 19 +++++++++++++++- 84_Super_Star_Trek/rust/tasks.md | 3 +++ 4 files changed, 57 insertions(+), 18 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 589e43e0..2afded1c 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -1,5 +1,6 @@ use std::process::exit; +use input::prompt; use model::{Galaxy, systems}; mod input; @@ -12,6 +13,8 @@ fn main() { .expect("Error setting Ctrl-C handler"); let mut galaxy = Galaxy::generate_new(); + let initial_klingons = galaxy.remaining_klingons(); + let initial_stardate = galaxy.stardate; view::enterprise(); view::intro(&galaxy); @@ -36,12 +39,21 @@ fn main() { _ => view::print_command_help() } - if galaxy.enterprise.destroyed || galaxy.enterprise.check_stranded() { + if galaxy.enterprise.destroyed || galaxy.enterprise.check_stranded() || galaxy.stardate >= galaxy.final_stardate { view::end_game_failure(&galaxy); - // todo check if can restart + if galaxy.remaining_klingons() > 0 && galaxy.remaining_starbases() > 0 && galaxy.stardate < galaxy.final_stardate { + view::replay(); + let result = prompt(""); + if result.len() > 0 && result[0].to_uppercase() == "AYE" { + galaxy.enterprise = Galaxy::new_captain(&galaxy.quadrants); + continue; + } + } + break; + } else if galaxy.remaining_klingons() == 0 { + let efficiency = 1000.0 * f32::powi(initial_klingons as f32 / (galaxy.stardate - initial_stardate), 2); + view::congratulations(efficiency); break; } - - // todo check for victory } } diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 151b85d3..2985f28a 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -101,7 +101,7 @@ impl Enterprise { } pub fn check_stranded(&self) -> bool { - if self.total_energy < 10 || (self.total_energy - self.shields < 10 && self.damaged.contains_key(systems::SHIELD_CONTROL)) { + if self.total_energy < 10 || (self.shields + 10 > self.total_energy && self.damaged.contains_key(systems::SHIELD_CONTROL)) { view::stranded(); return true; } @@ -213,28 +213,35 @@ impl Galaxy { let quadrants = Self::generate_quadrants(); let mut rng = rand::thread_rng(); - let enterprise_quadrant = Pos(rng.gen_range(0..8), rng.gen_range(0..8)); - let enterprise_sector = quadrants[enterprise_quadrant.as_index()].find_empty_sector(); let stardate = rng.gen_range(20..=40) as f32 * 100.0; + let enterprise = Self::new_captain(&quadrants); + let mut scanned = HashSet::new(); - scanned.insert(enterprise_quadrant); + scanned.insert(enterprise.quadrant); Galaxy { stardate, final_stardate: stardate + rng.gen_range(25..=35) as f32, quadrants: quadrants, scanned: scanned, - enterprise: Enterprise { - destroyed: false, - damaged: HashMap::new(), - quadrant: enterprise_quadrant, - sector: enterprise_sector, - photon_torpedoes: MAX_PHOTON_TORPEDOES, - total_energy: MAX_ENERGY, - shields: 0 } + enterprise: enterprise } - } + } + + pub fn new_captain(quadrants: &Vec) -> Enterprise { + let mut rng = rand::thread_rng(); + let enterprise_quadrant = Pos(rng.gen_range(0..8), rng.gen_range(0..8)); + let enterprise_sector = quadrants[enterprise_quadrant.as_index()].find_empty_sector(); + Enterprise { + destroyed: false, + damaged: HashMap::new(), + quadrant: enterprise_quadrant, + sector: enterprise_sector, + photon_torpedoes: MAX_PHOTON_TORPEDOES, + total_energy: MAX_ENERGY, + shields: 0 } + } fn generate_quadrants() -> Vec { let mut rng = rand::thread_rng(); diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 5372cc6b..f54bd4cc 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -417,7 +417,7 @@ pub fn galaxy_region_map() { } } -pub(crate) fn galaxy_scanned_map(galaxy: &Galaxy) { +pub fn galaxy_scanned_map(galaxy: &Galaxy) { println!( "Computer record of galaxy for quadrant {} 1 2 3 4 5 6 7 8 @@ -477,3 +477,20 @@ pub fn klingon_remaining_energy(energy: f32) { pub fn klingon_destroyed() { println!(" Target Destroyed!") // not standard for game but feedback is good. Sorry Mr. Roddenberry } + +pub fn congratulations(efficiency: f32) { + println!(" +Congratulations, Captain! The last Klingon battle cruiser +menacing the Federation has been destroyed. + +Your efficiency rating is {efficiency}. + ") +} + +pub fn replay() { + println!(" + +The Federation is in need of a new starship commander +for a similar mission -- if there is a volunteer +let him step forward and enter 'Aye'") +} \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 06bcf57a..5dc79803 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -37,7 +37,10 @@ Started after movement and display of stats was finished (no energy management o - [x] 5 - galactic name map - [ ] restarting the game + - after defeat + - and by resigning - [ ] time progression - check all areas where time should move, and adjust accordingly + - [ ] defeat due to time expired - [ ] intro instructions - [ ] victory From 7008127806f5ae4d52290ecc0a044fa57445c5fb Mon Sep 17 00:00:00 2001 From: Christopher Date: Fri, 3 Mar 2023 16:17:09 +1300 Subject: [PATCH 073/141] made it so you can still repair at starbases with a broken damage control --- 84_Super_Star_Trek/rust/src/commands.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 6b2d4b08..c878ae26 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -231,11 +231,10 @@ pub fn run_damage_control(galaxy: &mut Galaxy) { if ship.damaged.contains_key(systems::DAMAGE_CONTROL) { view::inoperable(&systems::name_for(systems::DAMAGE_CONTROL)); - return; + } else { + view::damage_control(&ship); } - view::damage_control(&ship); - let quadrant = &galaxy.quadrants[ship.quadrant.as_index()]; if ship.damaged.len() == 0 || !quadrant.docked_at_starbase(ship.sector) { return; From 51cfce4fb8dfc965eb52058bd05b3e92cfdbdf2c Mon Sep 17 00:00:00 2001 From: Christopher Date: Sat, 4 Mar 2023 09:07:51 +1300 Subject: [PATCH 074/141] minor tweak to fix another underflow issue --- 84_Super_Star_Trek/rust/src/main.rs | 2 +- 84_Super_Star_Trek/rust/src/model.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 2afded1c..5c9e1058 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -39,7 +39,7 @@ fn main() { _ => view::print_command_help() } - if galaxy.enterprise.destroyed || galaxy.enterprise.check_stranded() || galaxy.stardate >= galaxy.final_stardate { + if galaxy.enterprise.destroyed || galaxy.enterprise.is_stranded() || galaxy.stardate >= galaxy.final_stardate { view::end_game_failure(&galaxy); if galaxy.remaining_klingons() > 0 && galaxy.remaining_starbases() > 0 && galaxy.stardate < galaxy.final_stardate { view::replay(); diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 2985f28a..144a7e93 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -58,13 +58,13 @@ impl Enterprise { view::enterprise_hit(&hit_strength, §or); - self.shields = (self.shields - hit_strength).max(0); - - if self.shields <= 0 { + if self.shields <= hit_strength { view::enterprise_destroyed(); self.destroyed = true } + self.shields -= hit_strength; + view::shields_hit(self.shields); if hit_strength >= 20 { @@ -100,7 +100,7 @@ impl Enterprise { return false; } - pub fn check_stranded(&self) -> bool { + pub fn is_stranded(&self) -> bool { if self.total_energy < 10 || (self.shields + 10 > self.total_energy && self.damaged.contains_key(systems::SHIELD_CONTROL)) { view::stranded(); return true; From 32c1508a5138dba883cb6b09cea42734abc02664 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sat, 4 Mar 2023 09:10:43 +1300 Subject: [PATCH 075/141] can now resign --- 84_Super_Star_Trek/rust/src/main.rs | 1 + 84_Super_Star_Trek/rust/src/view.rs | 1 - 84_Super_Star_Trek/rust/tasks.md | 10 +++++----- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 5c9e1058..a7f11d2a 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -36,6 +36,7 @@ fn main() { systems::LONG_RANGE_SCAN => commands::perform_long_range_scan(&mut galaxy), systems::COMPUTER => commands::access_computer(&galaxy, command[1..].into()), systems::PHASERS => commands::get_power_and_fire_phasers(&mut galaxy, command[1..].into()), + "XXX" => galaxy.enterprise.destroyed = true, _ => view::print_command_help() } diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index f54bd4cc..73ec39d7 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -489,7 +489,6 @@ Your efficiency rating is {efficiency}. pub fn replay() { println!(" - The Federation is in need of a new starship commander for a similar mission -- if there is a volunteer let him step forward and enter 'Aye'") diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 5dc79803..7a663304 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -36,11 +36,11 @@ Started after movement and display of stats was finished (no energy management o - [ ] 4 - direction/distance calculator (useful for nav actions I guess) - [x] 5 - galactic name map -- [ ] restarting the game - - after defeat - - and by resigning +- [x] restarting the game + - [x] after defeat + - [x] and by resigning - [ ] time progression - check all areas where time should move, and adjust accordingly - - [ ] defeat due to time expired + - [x] defeat due to time expired - [ ] intro instructions -- [ ] victory +- [x] victory From 7fb940f18ec7d97f394f2d22668c1e41f7537cbd Mon Sep 17 00:00:00 2001 From: Christopher Date: Sat, 4 Mar 2023 09:26:24 +1300 Subject: [PATCH 076/141] started work on photon torpedoes (final system) also moved all prompt text into views submodule --- 84_Super_Star_Trek/rust/src/commands.rs | 24 ++++++++++++++++++------ 84_Super_Star_Trek/rust/src/main.rs | 7 ++++--- 84_Super_Star_Trek/rust/src/model.rs | 8 ++++++-- 84_Super_Star_Trek/rust/src/view.rs | 18 ++++++++++++++++++ 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index c878ae26..98b9e0d8 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -19,7 +19,7 @@ pub fn get_amount_and_set_shields(galaxy: &mut Galaxy, provided: Vec) { } view::energy_available(galaxy.enterprise.total_energy); - let value = input::param_or_prompt_value(&provided, 0, "Number of units to shields", 0, i32::MAX); + let value = input::param_or_prompt_value(&provided, 0, view::prompts::SHIELDS, 0, i32::MAX); if value.is_none() { view::shields_unchanged(); return; @@ -38,7 +38,7 @@ pub fn get_amount_and_set_shields(galaxy: &mut Galaxy, provided: Vec) { pub fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy, provided: Vec) { - let course = input::param_or_prompt_value(&provided, 0, "Course (1-9)?", 1, 9); + let course = input::param_or_prompt_value(&provided, 0, view::prompts::COURSE, 1, 9); if course.is_none() { view::bad_nav(); return; @@ -51,7 +51,7 @@ pub fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy, provided: Vec max_warp = 0.2; } - let speed = input::param_or_prompt_value(&provided, 1, format!("Warp Factor (0-{})?", max_warp).as_str(), 0.0, 8.0); + let speed = input::param_or_prompt_value(&provided, 1, &view::prompts::warp_factor(max_warp), 0.0, 8.0); if speed.is_none() { view::bad_nav(); return; @@ -244,7 +244,7 @@ pub fn run_damage_control(galaxy: &mut Galaxy) { let repair_time = (ship.damaged.len() as f32 * 0.1 + repair_delay).max(0.9); view::repair_estimate(repair_time); - if !input::prompt_yes_no("Will you authorize the repair order") { + if !input::prompt_yes_no(view::prompts::REPAIR) { return; } @@ -273,7 +273,7 @@ pub fn access_computer(galaxy: &Galaxy, provided: Vec) { let operation : i32; loop { - let entered = input::param_or_prompt_value(&provided, 0, "Computer active and waiting command?", 0, 5); + let entered = input::param_or_prompt_value(&provided, 0, view::prompts::COMPUTER, 0, 5); if entered.is_none() { view::computer_options(); } else { @@ -310,7 +310,7 @@ pub fn get_power_and_fire_phasers(galaxy: &mut Galaxy, provided: Vec) { view::phasers_locked(available_energy); let mut power: f32; loop { - let setting = param_or_prompt_value(&provided, 0, "Number of units to fire", 0, available_energy); + let setting = param_or_prompt_value(&provided, 0, view::prompts::PHASERS, 0, available_energy); if setting.is_some() { power = setting.unwrap() as f32; break; @@ -349,4 +349,16 @@ pub fn get_power_and_fire_phasers(galaxy: &mut Galaxy, provided: Vec) { quadrant.klingons.retain(|k| k.energy > 0.0); klingons_fire(galaxy); +} + +pub fn gather_dir_and_launch_torpedo(galaxy: &mut Galaxy, provided: Vec) { + if galaxy.enterprise.damaged.contains_key(systems::TORPEDOES) { + view::inoperable(&systems::name_for(systems::TORPEDOES)); + return; + } + + if galaxy.enterprise.photon_torpedoes == 0 { + view::no_torpedoes_remaining(); + return; + } } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index a7f11d2a..58be1f18 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -18,13 +18,13 @@ fn main() { view::enterprise(); view::intro(&galaxy); - let _ = input::prompt("Press Enter when ready to accept command"); + let _ = input::prompt(view::prompts::WHEN_READY); view::starting_quadrant(&galaxy.enterprise.quadrant); view::short_range_scan(&galaxy); loop { - let command = input::prompt("Command?"); + let command = input::prompt(view::prompts::COMMAND); if command.len() == 0 { continue; } @@ -36,7 +36,8 @@ fn main() { systems::LONG_RANGE_SCAN => commands::perform_long_range_scan(&mut galaxy), systems::COMPUTER => commands::access_computer(&galaxy, command[1..].into()), systems::PHASERS => commands::get_power_and_fire_phasers(&mut galaxy, command[1..].into()), - "XXX" => galaxy.enterprise.destroyed = true, + systems::TORPEDOES => commands::gather_dir_and_launch_torpedo(&mut galaxy, command[1..].into()), + systems::RESIGN => galaxy.enterprise.destroyed = true, _ => view::print_command_help() } diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 144a7e93..58251919 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -118,9 +118,12 @@ pub mod systems { pub const LONG_RANGE_SCAN: &str = "LRS"; pub const COMPUTER: &str = "COM"; pub const PHASERS: &str = "PHA"; + pub const TORPEDOES: &str = "TOR"; - pub const KEYS: [&str; 7] = [ - SHORT_RANGE_SCAN, WARP_ENGINES, SHIELD_CONTROL, DAMAGE_CONTROL, LONG_RANGE_SCAN, COMPUTER, PHASERS + pub const RESIGN: &str = "XXX"; + + pub const KEYS: [&str; 8] = [ + SHORT_RANGE_SCAN, WARP_ENGINES, SHIELD_CONTROL, DAMAGE_CONTROL, LONG_RANGE_SCAN, COMPUTER, PHASERS, TORPEDOES ]; pub fn name_for(key: &str) -> String { @@ -132,6 +135,7 @@ pub mod systems { LONG_RANGE_SCAN => "Long Range Scanners".into(), COMPUTER => "Library-Computer".into(), PHASERS => "Phaser Control".into(), + TORPEDOES => "Photon Tubes".into(), _ => "Unknown".into() } } diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 73ec39d7..b087a949 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -1,5 +1,19 @@ use crate::model::{Galaxy, Pos, EndPosition, SectorStatus, Enterprise, systems}; +pub mod prompts { + pub const COURSE: &str = "Course (1-9)?"; + pub const SHIELDS: &str = "Number of units to shields"; + pub const REPAIR: &str = "Will you authorize the repair order"; + pub const COMPUTER: &str = "Computer active and waiting command?"; + pub const PHASERS: &str = "Number of units to fire"; + pub const WHEN_READY: &str = "Press Enter when ready to accept command"; + pub const COMMAND: &str = "Command?"; + + pub fn warp_factor(max_warp: f32) -> String { + format!("Warp Factor (0-{})?", max_warp) + } +} + pub fn full_instructions() { println!( " INSTRUCTIONS FOR 'SUPER STAR TREK' @@ -492,4 +506,8 @@ pub fn replay() { The Federation is in need of a new starship commander for a similar mission -- if there is a volunteer let him step forward and enter 'Aye'") +} + +pub fn no_torpedoes_remaining() { + println!("All photon torpedoes expended") } \ No newline at end of file From 3344649ed4a35f579529f12271a29ee6b05c619d Mon Sep 17 00:00:00 2001 From: Christopher Date: Sat, 4 Mar 2023 11:36:42 +1300 Subject: [PATCH 077/141] changed nav function to calculate a path --- 84_Super_Star_Trek/rust/src/commands.rs | 86 +++++++++++++++++-------- 84_Super_Star_Trek/rust/src/model.rs | 19 +++--- 84_Super_Star_Trek/rust/src/view.rs | 9 +++ 3 files changed, 79 insertions(+), 35 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 98b9e0d8..b3ca531e 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -38,7 +38,7 @@ pub fn get_amount_and_set_shields(galaxy: &mut Galaxy, provided: Vec) { pub fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy, provided: Vec) { - let course = input::param_or_prompt_value(&provided, 0, view::prompts::COURSE, 1, 9); + let course = input::param_or_prompt_value(&provided, 0, view::prompts::COURSE, 1.0, 9.0); if course.is_none() { view::bad_nav(); return; @@ -126,28 +126,28 @@ fn repair_or_damage_random_system(enterprise: &mut Enterprise) { view::random_repair_report_for(system_name, true); } -fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { +fn move_enterprise(course: f32, warp_speed: f32, galaxy: &mut Galaxy) { let ship = &mut galaxy.enterprise; // todo account for being blocked - let end = find_end_quadrant_sector(ship.quadrant, ship.sector, course, warp_speed); + let path = find_path(ship.quadrant, ship.sector, course, warp_speed); - if end.energy_cost > ship.total_energy { + if path.energy_cost > ship.total_energy { view::insuffient_warp_energy(warp_speed); return } - if end.hit_edge { - view::hit_edge(&end); + if path.hit_edge { + view::hit_edge(&path); } - if ship.quadrant != end.quadrant { - view::enter_quadrant(&end.quadrant); - galaxy.scanned.insert(end.quadrant); + if ship.quadrant != path.quadrant { + view::enter_quadrant(&path.quadrant); + galaxy.scanned.insert(path.quadrant); - if galaxy.quadrants[end.quadrant.as_index()].klingons.len() > 0 { + if galaxy.quadrants[path.quadrant.as_index()].klingons.len() > 0 { view::condition_red(); if ship.shields <= 200 { view::danger_shields(); @@ -155,16 +155,16 @@ fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { } } - ship.quadrant = end.quadrant; - ship.sector = end.sector; + ship.quadrant = path.quadrant; + ship.sector = path.sector; - let quadrant = &galaxy.quadrants[end.quadrant.as_index()]; + let quadrant = &galaxy.quadrants[path.quadrant.as_index()]; if quadrant.docked_at_starbase(ship.sector) { ship.shields = 0; ship.photon_torpedoes = MAX_PHOTON_TORPEDOES; ship.total_energy = MAX_ENERGY; } else { - ship.total_energy = (ship.total_energy - end.energy_cost).max(0); + ship.total_energy = (ship.total_energy - path.energy_cost).max(0); if ship.shields > ship.total_energy { view::divert_energy_from_shields(); ship.shields = ship.total_energy; @@ -174,26 +174,45 @@ fn move_enterprise(course: u8, warp_speed: f32, galaxy: &mut Galaxy) { view::short_range_scan(&galaxy) } -fn find_end_quadrant_sector(start_quadrant: Pos, start_sector: Pos, course: u8, warp_speed: f32) -> EndPosition { - let (dx, dy): (i8, i8) = COURSES[(course - 1) as usize]; +fn find_path(start_quadrant: Pos, start_sector: Pos, course: f32, warp_speed: f32) -> EndPosition { + + // this course delta stuff is a translation (of a translation, of a translation...) of the original basic calcs + let dir = (course - 1.0) % 8.0; + let (dx1, dy1) = COURSES[dir as usize]; + let (dx2, dy2) = COURSES[(dir + 1.0) as usize]; + let frac = dir - (dir as i32) as f32; + + let dx = dx1 + (dx2 - dx1) * frac; + let dy = dy1 + (dy2 - dy1) * frac; let mut distance = (warp_speed * 8.0) as i8; if distance == 0 { distance = 1; } - let galaxy_pos = start_quadrant * 8u8 + start_sector; + let mut last_sector = start_quadrant * 8 + start_sector; + let mut path = Vec::new(); + let mut hit_edge; - let mut nx = (galaxy_pos.0 as i8) + dx * distance; - let mut ny = (galaxy_pos.1 as i8) + dy * distance; + loop { + let nx = (last_sector.0 as f32 + dx) as i8; + let ny = (last_sector.1 as f32 + dy) as i8; + hit_edge = nx < 0 || ny < 0 || nx >= 64 || ny >= 64; + if hit_edge { + break; + } + last_sector = Pos(nx as u8, ny as u8); + path.push(last_sector); - let hit_edge = nx < 0 || ny < 0 || nx >= 64 || ny >= 64; - nx = nx.min(63).max(0); - ny = ny.min(63).max(0); - - let quadrant = Pos((nx / 8) as u8, (ny / 8) as u8); - let sector = Pos((nx % 8) as u8, (ny % 8) as u8); - let energy_cost = distance as u16 + 10; + distance -= 1; + if distance == 0 { + break; + } + } + + let quadrant = Pos((last_sector.0 / 8) as u8, (last_sector.1 / 8) as u8); + let sector = Pos((last_sector.0 % 8) as u8, (last_sector.1 % 8) as u8); + let energy_cost = path.len() as u16 + 10; EndPosition { quadrant, sector, hit_edge, energy_cost } } @@ -361,4 +380,19 @@ pub fn gather_dir_and_launch_torpedo(galaxy: &mut Galaxy, provided: Vec) view::no_torpedoes_remaining(); return; } + + let course = input::param_or_prompt_value(&provided, 0, view::prompts::TORPEDO_COURSE, 1.0, 9.0); + if course.is_none() { + view::bad_torpedo_course(); + return; + } + + galaxy.enterprise.photon_torpedoes -= 1; + view::torpedo_track(); + + // calculate direction + // step through sectors + // test for hits or final miss + + klingons_fire(galaxy); } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 58251919..a8919b5e 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -183,15 +183,16 @@ impl Display for Pos { } } -pub const COURSES : [(i8, i8); 8] = [ - (1, 0), - (1, -1), - (0, -1), - (-1, -1), - (-1, 0), - (-1, 1), - (0, 1), - (1, 1), +pub const COURSES : [(f32, f32); 9] = [ + (1., 0.), + (1., -1.), + (0., -1.), + (-1., -1.), + (-1., 0.), + (-1., 1.), + (0., 1.), + (1., 1.), + (1., 0.), // course 9 is equal to course 1 ]; #[derive(PartialEq)] diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index b087a949..38e307df 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -2,6 +2,7 @@ use crate::model::{Galaxy, Pos, EndPosition, SectorStatus, Enterprise, systems}; pub mod prompts { pub const COURSE: &str = "Course (1-9)?"; + pub const TORPEDO_COURSE: &str = "Photon torpedo course (1-9)?"; pub const SHIELDS: &str = "Number of units to shields"; pub const REPAIR: &str = "Will you authorize the repair order"; pub const COMPUTER: &str = "Computer active and waiting command?"; @@ -276,6 +277,10 @@ pub fn bad_nav() { println!(" Lt. Sulu reports, 'Incorrect course data, sir!'") } +pub fn bad_torpedo_course() { + println!(" Ensign Chekov reports, 'Incorrect course data, sir!'") +} + pub fn enterprise_hit(hit_strength: &u16, from_sector: &Pos) { println!("{hit_strength} unit hit on Enterprise from sector {from_sector}"); } @@ -510,4 +515,8 @@ let him step forward and enter 'Aye'") pub fn no_torpedoes_remaining() { println!("All photon torpedoes expended") +} + +pub fn torpedo_track() { + println!("Torpedo track:") } \ No newline at end of file From 5c25a83eafe9a8c062c0aed79e30262206cad81a Mon Sep 17 00:00:00 2001 From: Christopher Date: Sat, 4 Mar 2023 11:37:32 +1300 Subject: [PATCH 078/141] bug fix --- 84_Super_Star_Trek/rust/src/view.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 38e307df..267fc91b 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -291,7 +291,7 @@ pub fn hit_edge(end: &EndPosition) { 'Permission to attempt crossing of galactic perimeter is hereby *Denied*. Shut down your engines.' Chief Engineer Scott reports, 'Warp engines shut down - at sector {} of quadrant {}.'", end.quadrant, end.sector); + at sector {} of quadrant {}.'", end.sector, end.quadrant); } pub fn condition_red() { From 781d0566f8d351e38ba273854ba0fb1c624df180 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sat, 4 Mar 2023 15:24:05 +1300 Subject: [PATCH 079/141] implemented torpedoes! --- 84_Super_Star_Trek/rust/src/commands.rs | 127 ++++++++++++++++++------ 84_Super_Star_Trek/rust/src/main.rs | 2 +- 84_Super_Star_Trek/rust/src/model.rs | 29 ++++-- 84_Super_Star_Trek/rust/src/view.rs | 39 ++++++-- 84_Super_Star_Trek/rust/tasks.md | 4 +- 5 files changed, 148 insertions(+), 53 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index b3ca531e..cc79dffb 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -132,22 +132,24 @@ fn move_enterprise(course: f32, warp_speed: f32, galaxy: &mut Galaxy) { // todo account for being blocked - let path = find_path(ship.quadrant, ship.sector, course, warp_speed); + let (path, hit_edge) = find_nav_path(ship.quadrant, ship.sector, course, warp_speed); + let energy_cost = path.len() as u16 + 10; - if path.energy_cost > ship.total_energy { + if energy_cost > ship.total_energy { view::insuffient_warp_energy(warp_speed); return } - if path.hit_edge { - view::hit_edge(&path); + let (end_quadrant, end_sector) = path[path.len() - 1].to_local_quadrant_sector(); + if hit_edge { + view::hit_edge(end_quadrant, end_sector); } - if ship.quadrant != path.quadrant { - view::enter_quadrant(&path.quadrant); - galaxy.scanned.insert(path.quadrant); + if ship.quadrant != end_quadrant { + view::enter_quadrant(end_quadrant); + galaxy.scanned.insert(end_quadrant); - if galaxy.quadrants[path.quadrant.as_index()].klingons.len() > 0 { + if galaxy.quadrants[end_quadrant.as_index()].klingons.len() > 0 { view::condition_red(); if ship.shields <= 200 { view::danger_shields(); @@ -155,16 +157,16 @@ fn move_enterprise(course: f32, warp_speed: f32, galaxy: &mut Galaxy) { } } - ship.quadrant = path.quadrant; - ship.sector = path.sector; + ship.quadrant = end_quadrant; + ship.sector = end_sector; - let quadrant = &galaxy.quadrants[path.quadrant.as_index()]; + let quadrant = &galaxy.quadrants[end_quadrant.as_index()]; if quadrant.docked_at_starbase(ship.sector) { ship.shields = 0; ship.photon_torpedoes = MAX_PHOTON_TORPEDOES; ship.total_energy = MAX_ENERGY; } else { - ship.total_energy = (ship.total_energy - path.energy_cost).max(0); + ship.total_energy = ship.total_energy - energy_cost; if ship.shields > ship.total_energy { view::divert_energy_from_shields(); ship.shields = ship.total_energy; @@ -174,23 +176,16 @@ fn move_enterprise(course: f32, warp_speed: f32, galaxy: &mut Galaxy) { view::short_range_scan(&galaxy) } -fn find_path(start_quadrant: Pos, start_sector: Pos, course: f32, warp_speed: f32) -> EndPosition { +fn find_nav_path(start_quadrant: Pos, start_sector: Pos, course: f32, warp_speed: f32) -> (Vec, bool) { - // this course delta stuff is a translation (of a translation, of a translation...) of the original basic calcs - let dir = (course - 1.0) % 8.0; - let (dx1, dy1) = COURSES[dir as usize]; - let (dx2, dy2) = COURSES[(dir + 1.0) as usize]; - let frac = dir - (dir as i32) as f32; - - let dx = dx1 + (dx2 - dx1) * frac; - let dy = dy1 + (dy2 - dy1) * frac; + let (dx, dy) = calculate_delta(course); let mut distance = (warp_speed * 8.0) as i8; if distance == 0 { distance = 1; } - let mut last_sector = start_quadrant * 8 + start_sector; + let mut last_sector = start_sector.as_galactic_sector(start_quadrant); let mut path = Vec::new(); let mut hit_edge; @@ -210,11 +205,20 @@ fn find_path(start_quadrant: Pos, start_sector: Pos, course: f32, warp_speed: f3 } } - let quadrant = Pos((last_sector.0 / 8) as u8, (last_sector.1 / 8) as u8); - let sector = Pos((last_sector.0 % 8) as u8, (last_sector.1 % 8) as u8); - let energy_cost = path.len() as u16 + 10; + (path, hit_edge) +} - EndPosition { quadrant, sector, hit_edge, energy_cost } +fn calculate_delta(course: f32) -> (f32, f32) { + // this course delta stuff is a translation (of a translation, of a translation...) of the original basic calcs + let dir = (course - 1.0) % 8.0; + let (dx1, dy1) = COURSES[dir as usize]; + let (dx2, dy2) = COURSES[(dir + 1.0) as usize]; + let frac = dir - (dir as i32) as f32; + + let dx = dx1 + (dx2 - dx1) * frac; + let dy = dy1 + (dy2 - dy1) * frac; + + (dx, dy) } fn klingons_move(galaxy: &mut Galaxy) { @@ -371,12 +375,15 @@ pub fn get_power_and_fire_phasers(galaxy: &mut Galaxy, provided: Vec) { } pub fn gather_dir_and_launch_torpedo(galaxy: &mut Galaxy, provided: Vec) { - if galaxy.enterprise.damaged.contains_key(systems::TORPEDOES) { + let star_bases = galaxy.remaining_starbases(); + let ship = &mut galaxy.enterprise; + + if ship.damaged.contains_key(systems::TORPEDOES) { view::inoperable(&systems::name_for(systems::TORPEDOES)); return; } - if galaxy.enterprise.photon_torpedoes == 0 { + if ship.photon_torpedoes == 0 { view::no_torpedoes_remaining(); return; } @@ -387,12 +394,68 @@ pub fn gather_dir_and_launch_torpedo(galaxy: &mut Galaxy, provided: Vec) return; } - galaxy.enterprise.photon_torpedoes -= 1; + ship.photon_torpedoes -= 1; view::torpedo_track(); - // calculate direction - // step through sectors - // test for hits or final miss + let path = find_torpedo_path(ship.sector, course.unwrap()); + let quadrant = &mut galaxy.quadrants[ship.quadrant.as_index()]; + let mut hit = false; + for p in path { + view::torpedo_path(p); + match quadrant.sector_status(p) { + SectorStatus::Empty => continue, + SectorStatus::Star => { + hit = true; + view::star_absorbed_torpedo(p); + break; + }, + SectorStatus::Klingon => { + hit = true; + quadrant.get_klingon(p).unwrap().energy = 0.0; + quadrant.klingons.retain(|k| k.energy > 0.0); + view::klingon_destroyed(); + break; + }, + SectorStatus::StarBase => { + hit = true; + quadrant.star_base = None; + let remaining = star_bases - 1; + view::destroyed_starbase(remaining > 0); + if remaining == 0 { + ship.destroyed = true; + } + break; + } + } + } + + if ship.destroyed { // if you wiped out the last starbase, trigger game over + return; + } + + if !hit { + view::torpedo_missed(); + } klingons_fire(galaxy); +} + +fn find_torpedo_path(start_sector: Pos, course: f32) -> Vec { + + let (dx, dy) = calculate_delta(course); + + let mut last_sector = start_sector; + let mut path = Vec::new(); + + loop { + let nx = (last_sector.0 as f32 + dx) as i8; + let ny = (last_sector.1 as f32 + dy) as i8; + if nx < 0 || ny < 0 || nx >= 8 || ny >= 8 { + break; + } + last_sector = Pos(nx as u8, ny as u8); + path.push(last_sector); + } + + path } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 58be1f18..78e59531 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -20,7 +20,7 @@ fn main() { view::intro(&galaxy); let _ = input::prompt(view::prompts::WHEN_READY); - view::starting_quadrant(&galaxy.enterprise.quadrant); + view::starting_quadrant(galaxy.enterprise.quadrant); view::short_range_scan(&galaxy); loop { diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index a8919b5e..9b2f1118 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -60,7 +60,8 @@ impl Enterprise { if self.shields <= hit_strength { view::enterprise_destroyed(); - self.destroyed = true + self.destroyed = true; + return; } self.shields -= hit_strength; @@ -141,13 +142,6 @@ pub mod systems { } } -pub struct EndPosition { - pub quadrant: Pos, - pub sector: Pos, - pub hit_edge: bool, - pub energy_cost: u16, -} - #[derive(PartialEq, Clone, Copy, Debug, Hash, Eq)] pub struct Pos(pub u8, pub u8); @@ -159,6 +153,14 @@ impl Pos { pub fn abs_diff(&self, other: Pos) -> u8 { self.0.abs_diff(other.0) + self.1.abs_diff(other.1) } + + pub fn as_galactic_sector(&self, containing_quadrant: Pos) -> Self { + Pos(containing_quadrant.0 * 8 + self.0, containing_quadrant.1 * 8 + self.1) + } + + pub fn to_local_quadrant_sector(&self) -> (Self, Self) { + (Pos(self.0 / 8, self.1 / 8), Pos(self.0 % 8, self.1 % 8)) + } } impl Mul for Pos { @@ -286,7 +288,7 @@ impl Quadrant { SectorStatus::Star } else if self.is_starbase(sector) { SectorStatus::StarBase - } else if self.has_klingon(§or) { + } else if self.has_klingon(sector) { SectorStatus::Klingon } else { SectorStatus::Empty @@ -300,9 +302,14 @@ impl Quadrant { } } - fn has_klingon(&self, sector: &Pos) -> bool { + fn has_klingon(&self, sector: Pos) -> bool { let klingons = &self.klingons; - klingons.into_iter().find(|k| &k.sector == sector).is_some() + klingons.into_iter().find(|k| k.sector == sector).is_some() + } + + pub fn get_klingon(&mut self, sector: Pos) -> Option<&mut Klingon> { + let klingons = &mut self.klingons; + klingons.into_iter().find(|k| k.sector == sector) } pub fn find_empty_sector(&self) -> Pos { diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 267fc91b..f7bac4db 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -1,4 +1,4 @@ -use crate::model::{Galaxy, Pos, EndPosition, SectorStatus, Enterprise, systems}; +use crate::model::{Galaxy, Pos, SectorStatus, Enterprise, systems}; pub mod prompts { pub const COURSE: &str = "Course (1-9)?"; @@ -187,19 +187,19 @@ const REGION_NAMES: [&str; 16] = [ const SUB_REGION_NAMES: [&str; 4] = ["I", "II", "III", "IV"]; -fn quadrant_name(quadrant: &Pos) -> String { +fn quadrant_name(quadrant: Pos) -> String { format!("{} {}", REGION_NAMES[((quadrant.1 << 1) + (quadrant.0 >> 2)) as usize], SUB_REGION_NAMES[(quadrant.1 % 4) as usize]) } -pub fn starting_quadrant(quadrant: &Pos) { +pub fn starting_quadrant(quadrant: Pos) { println!( "\nYour mission begins with your starship located in the galactic quadrant, '{}'.\n", quadrant_name(quadrant)) } -pub fn enter_quadrant(quadrant: &Pos) { +pub fn enter_quadrant(quadrant: Pos) { println!("\nNow entering {} quadrant . . .\n", quadrant_name(quadrant)) } @@ -285,13 +285,13 @@ pub fn enterprise_hit(hit_strength: &u16, from_sector: &Pos) { println!("{hit_strength} unit hit on Enterprise from sector {from_sector}"); } -pub fn hit_edge(end: &EndPosition) { +pub fn hit_edge(quadrant: Pos, sector: Pos) { println!( "Lt. Uhura report message from Starfleet Command: 'Permission to attempt crossing of galactic perimeter is hereby *Denied*. Shut down your engines.' Chief Engineer Scott reports, 'Warp engines shut down - at sector {} of quadrant {}.'", end.sector, end.quadrant); + at sector {} of quadrant {}.'", sector, quadrant); } pub fn condition_red() { @@ -494,7 +494,7 @@ pub fn klingon_remaining_energy(energy: f32) { } pub fn klingon_destroyed() { - println!(" Target Destroyed!") // not standard for game but feedback is good. Sorry Mr. Roddenberry + println!("*** Klingon destroyed ***") } pub fn congratulations(efficiency: f32) { @@ -519,4 +519,29 @@ pub fn no_torpedoes_remaining() { pub fn torpedo_track() { println!("Torpedo track:") +} + +pub fn torpedo_path(sector: Pos) { + println!("{:<16}{}", "", sector) +} + +pub fn torpedo_missed() { + println!("Torpedo missed!") +} + +pub fn star_absorbed_torpedo(sector: Pos) { + println!("Star at {sector} absorbed torpedo energy.") +} + +pub fn destroyed_starbase(not_the_last_starbase: bool) { + println!("*** Starbase destroyed ***"); + if not_the_last_starbase { + println!(" +Starfleet Command reviewing your record to consider +court martial!") + } else { + println!(" +That does it, Captain!! You are hereby relieved of command +and sentenced to 99 stardates at hard labor on Cygnus 12!!") + } } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 7a663304..2ddea3df 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -25,9 +25,9 @@ Started after movement and display of stats was finished (no energy management o - [x] proximity detection for docking - [x] repair on damage control - [x] protection from shots -- [ ] weapons +- [x] weapons - [x] phasers - - [ ] torpedoes + - [x] torpedoes - [ ] computer - [x] 0 - output of all short and long range scans (requires tracking if a system has been scanned) - [ ] 1 - klingons, starbases, stardate and damage control From ca4b4a9373be89ad84546997f66ff225f087e865 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Sat, 4 Mar 2023 18:54:49 +1100 Subject: [PATCH 080/141] Rename variables --- 77_Salvo/csharp/Game.cs | 449 +++++++++++++++++++++------------------- 77_Salvo/salvo.bas | 2 + 2 files changed, 233 insertions(+), 218 deletions(-) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index e7a7f818..02cab91e 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -17,410 +17,423 @@ internal class Game { _io.Write(Streams.Title); -L1040: var AA = new float[11,11]; - var BA = new float[11,11]; - var CA = new int[8]; - var DA = new int[8]; - var EA = new int[13]; - var FA = new int[13]; - var GA = new int[13]; - var HA = new float[13]; - var KA = new int[11,11]; -L1050: var Z8 = 0; +L1040: var computerGrid = new float[11,11]; + var humanGrid = new float[11,11]; + var shotsX = new int[8]; + var shotsY = new int[8]; + var hitTurnRecord = new int[13]; + var tempX = new int[13]; + var tempY = new int[13]; + var hitShipValue = new float[13]; + var temp = new int[11,11]; L1060: for (var W = 1; W <= 12; W++) { -L1070: EA[W] = -1; -L1080: HA[W] = -1; +L1070: hitTurnRecord[W] = -1; +L1080: hitShipValue[W] = -1; } L1100: for (var X = 1; X <= 10; X++) { L1110: for (var Y = 1; Y <= 10; Y++) { -L1120: BA[X,Y] = 0; +L1120: humanGrid[X,Y] = 0; } } L1150: for (var X = 1; X <= 12; X++) { -L1160: FA[X]=0; -L1170: GA[X]=0; +L1160: tempX[X]=0; +L1170: tempY[X]=0; } L1190: for (var X = 1; X <= 10; X++) { L1200: for (var Y = 1; Y <= 10; Y++) { -L1210: AA[X,Y]=0; +L1210: computerGrid[X,Y]=0; } } L1240: for (var K = 4; K >= 1; K--) { -L1250: var U6=0; -L1260: var (X, Y, V, V2) = L2910(); -L1270: int FNA(int K) => (5-K)*3-2*K/4+Math.Sign(K-1)-1; -L1280: int FNB(int K) => K+K/4-Math.Sign(K-1); -L1290: if (V+V2+V*V2 == 0) { goto L1260; } -L1300: if (Y+V*FNB(K)>10) { goto L1260; } -L1310: if (Y+V*FNB(K)<1) { goto L1260; } -L1320: if (X+V2*FNB(K)>10) { goto L1260; } -L1330: if (X+V2*FNB(K)<1) { goto L1260; } -L1340: U6=U6+1; -L1350: if (U6>25) { goto L1190; } -L1360: for (var Z = 0; Z <= FNB(K); Z++) +L1250: var shipGenerationAttempts=0; +L1260: var (startX, startY, deltaY, deltaX) = L2910(); +L1270: int GetFirstIndex(int shipNumber) => (5-shipNumber)*3-2*(shipNumber/4)+Math.Sign(shipNumber-1)-1; +L1280: int GetShipSizeLessOne(int shipNumber) => shipNumber + shipNumber/4 - Math.Sign(shipNumber-1); +L1290: if (deltaY+deltaX+deltaY*deltaX == 0) { goto L1260; } +L1300: if (startY+deltaY*GetShipSizeLessOne(K)>10) { goto L1260; } +L1310: if (startY+deltaY*GetShipSizeLessOne(K)<1) { goto L1260; } +L1320: if (startX+deltaX*GetShipSizeLessOne(K)>10) { goto L1260; } +L1330: if (startX+deltaX*GetShipSizeLessOne(K)<1) { goto L1260; } +L1340: shipGenerationAttempts=shipGenerationAttempts+1; +L1350: if (shipGenerationAttempts>25) { goto L1190; } + // determine ship coordinates +L1360: for (var i = 0; i <= GetShipSizeLessOne(K); i++) { -L1370: FA[Z+FNA(K)]=X+V2*Z; -L1380: GA[Z+FNA(K)]=Y+V*Z; + tempX[i+GetFirstIndex(K)]=startX+deltaX*i; +L1380: tempY[i+GetFirstIndex(K)]=startY+deltaY*i; } -L1400: var U8=FNA(K); -L1405: if (U8>U8+FNB(K)) { goto L1460; } -L1410: for (var Z2 = U8; Z2 <= U8+FNB(K); Z2++) +L1400: var firstIndex=GetFirstIndex(K); +L1405: if (firstIndex>firstIndex+GetShipSizeLessOne(K)) { goto L1460; } // Can't be true + // detect proximity to previous ships + for (var i = firstIndex; i <= firstIndex+GetShipSizeLessOne(K); i++) { -L1415: if (U8<2) { continue; } -L1420: for (var Z3 = 1; Z3 <= U8-1; Z3++) +L1415: if (firstIndex<2) { continue; } // Only true for the Battleship +L1420: for (var j = 1; j <= firstIndex-1; j++) { -L1430: if (Math.Sqrt((FA[Z3]-FA[Z2])*(FA[Z3]-FA[Z2]) + (GA[Z3]-GA[Z2])*(GA[Z3]-GA[Z2])) < 3.59) { goto L1260; } +L1430: if (Math.Sqrt((tempX[j]-tempX[i])*(tempX[j]-tempX[i]) + (tempY[j]-tempY[i])*(tempY[j]-tempY[i])) < 3.59) { goto L1260; } } } -L1460: for (var Z = 0; Z <= FNB(K); Z++) + // put ship on board +L1460: for (var i = 0; i <= GetShipSizeLessOne(K); i++) { -L1470: AA[FA[Z+U8],GA[Z+U8]]=.5F+Math.Sign(K-1)*(K-1.5F); +L1470: computerGrid[tempX[i+firstIndex],tempY[i+firstIndex]]=.5F+Math.Sign(K-1)*(K-1.5F); } } L1500: _io.WriteLine("ENTER COORDINATES FOR..."); L1510: _io.WriteLine("BATTLESHIP"); -L1520: for (var X = 1; X <= 5; X++) +L1520: for (var i = 1; i <= 5; i++) { - var (Y, Z) = _io.Read2Numbers(""); - BA[(int)Y, (int)Z] = 3; -L1540: BA[(int)Y, (int)Z] = 3; + var (x, y) = _io.Read2Numbers(""); +L1540: humanGrid[(int)x, (int)y] = 3; } L1560: _io.WriteLine("CRUISER"); -L1570: for (var X = 1; X <= 3; X++) +L1570: for (var i = 1; i <= 3; i++) { - var (Y, Z) = _io.Read2Numbers(""); -L1590: BA[(int)Y, (int)Z]=2; + var (x, y) = _io.Read2Numbers(""); +L1590: humanGrid[(int)x, (int)y]=2; } L1610: _io.WriteLine("DESTROYER"); -L1620: for (var X = 1; X <= 2; X++) +L1620: for (var i = 1; i <= 2; i++) { - var (Y, Z) = _io.Read2Numbers(""); -L1640: BA[(int)Y, (int)Z]=1; + var (x, y) = _io.Read2Numbers(""); +L1640: humanGrid[(int)x, (int)y]=1; } L1660: _io.WriteLine("DESTROYER"); -L1670: for (var X = 1; X <= 2; X++) +L1670: for (var i = 1; i <= 2; i++) { - var (Y, Z) = _io.Read2Numbers(""); -L1690: BA[(int)Y, (int)Z]=.5F; + var (x, y) = _io.Read2Numbers(""); +L1690: humanGrid[(int)x, (int)y]=.5F; } -L1710: var JS = _io.ReadString("DO YOU WANT TO START"); -L1730: if (JS != "WHERE ARE YOUR SHIPS?") { goto L1890; } +L1710: var startResponse = _io.ReadString("DO YOU WANT TO START"); +L1730: if (startResponse != "WHERE ARE YOUR SHIPS?") { goto L1890; } L1740: _io.WriteLine("BATTLESHIP"); -L1750: for (var Z = 1; Z <= 5; Z++) +L1750: for (var i = 1; i <= 5; i++) { -L1760: _io.WriteLine($" {FA[Z]} {GA[Z]} "); +L1760: _io.WriteLine($" {tempX[i]} {tempY[i]} "); } L1780: _io.WriteLine("CRUISER"); -L1790: _io.WriteLine($" {FA[6]} {GA[6]} "); -L1800: _io.WriteLine($" {FA[7]} {GA[7]} "); -L1810: _io.WriteLine($" {FA[8]} {GA[8]} "); +L1790: _io.WriteLine($" {tempX[6]} {tempY[6]} "); +L1800: _io.WriteLine($" {tempX[7]} {tempY[7]} "); +L1810: _io.WriteLine($" {tempX[8]} {tempY[8]} "); L1820: _io.WriteLine("DESTROYER"); -L1830: _io.WriteLine($" {FA[9]} {GA[9]} "); -L1840: _io.WriteLine($" {FA[10]} {GA[10]} "); +L1830: _io.WriteLine($" {tempX[9]} {tempY[9]} "); +L1840: _io.WriteLine($" {tempX[10]} {tempY[10]} "); L1850: _io.WriteLine("DESTROYER"); -L1860: _io.WriteLine($" {FA[11]} {GA[11]} "); -L1870: _io.WriteLine($" {FA[12]} {GA[12]} "); +L1860: _io.WriteLine($" {tempX[11]} {tempY[11]} "); +L1870: _io.WriteLine($" {tempX[12]} {tempY[12]} "); L1880: goto L1710; -L1890: var C=0; -L1900: var KS = _io.ReadString("DO YOU WANT TO SEE MY SHOTS"); +L1890: var turnNumber=0; +L1900: var seeShotsResponse = _io.ReadString("DO YOU WANT TO SEE MY SHOTS"); L1920: _io.WriteLine(); -L1930: if (JS != "YES") { goto L2620; } -L1950: if (JS != "YES") { goto L1990; } -L1960: C=C+1; +L1930: if (startResponse != "YES") { goto L2620; } +L1950: if (startResponse != "YES") { goto L1990; } +L1960: turnNumber=turnNumber+1; L1970: _io.WriteLine(); -L1980: _io.WriteLine($"TURN {C}"); -L1990: var A=0; -L2000: for (var W = .5F; W <= 3; W += .5F) +L1980: _io.WriteLine($"TURN {turnNumber}"); +L1990: var maxShotCount=0; +L2000: for (var shipValue = .5F; shipValue <= 3; shipValue += .5F) { -L2010: for (var X = 1; X <= 10; X++) +L2010: for (var x = 1; x <= 10; x++) { -L2020: for (var Y = 1; Y <= 10; Y++) +L2020: for (var y = 1; y <= 10; y++) { -L2030: if (BA[X,Y] == W) { goto L2070; } +L2030: if (humanGrid[x,y] == shipValue) { goto L2070; } } } L2060: continue; -L2070: A=A+(int)(W+.5F); +L2070: maxShotCount=maxShotCount+(int)(shipValue+.5F); } -L2090: for (var W = 1; W <= 7; W++) +L2090: for (var i = 1; i <= 7; i++) { -L2100: CA[W] = 0; -L2110: DA[W] = 0; -L2120: FA[W] = 0; -L2130: GA[W] = 0; +L2100: shotsX[i] = 0; +L2110: shotsY[i] = 0; +L2120: tempX[i] = 0; +L2130: tempY[i] = 0; } -L2150: var P3=0; -L2160: for (var X = 1; X <= 10; X++) +L2150: var untriedSquareCount=0; +L2160: for (var x = 1; x <= 10; x++) { -L2170: for (var Y = 1; Y <= 10; Y++) +L2170: for (var y = 1; y <= 10; y++) { -L2180: if (AA[X,Y]>10) { continue; } -L2190: P3=P3+1; +L2180: if (computerGrid[x,y]>10) { continue; } +L2190: untriedSquareCount=untriedSquareCount+1; } } -L2220: _io.WriteLine($"YOU HAVE {A} SHOTS."); -L2230: if (P3 >= A) { goto L2260; } +L2220: _io.WriteLine($"YOU HAVE {maxShotCount} SHOTS."); +L2230: if (untriedSquareCount >= maxShotCount) { goto L2260; } L2240: _io.WriteLine("YOU HAVE MORE SHOTS THAN THERE ARE BLANK SQUARES."); L2250: goto L2890; -L2260: if (A != 0) { goto L2290; } +L2260: if (maxShotCount != 0) { goto L2290; } L2270: _io.WriteLine("I HAVE WON."); L2280: return; -L2290: for (var W = 1; W <= A; W++) +L2290: for (var i = 1; i <= maxShotCount; i++) { -L2300: var (X, Y) = _io.Read2Numbers(""); -L2310: if (X != (int)X) { goto L2370; } -L2320: if (X > 10) { goto L2370; } -L2330: if (X < 1) { goto L2370; } -L2340: if (Y != (int)Y) { goto L2370; } -L2350: if (Y > 10) { goto L2370; } -L2360: if (Y >= 1) { goto L2390; } +L2300: var (x, y) = _io.Read2Numbers(""); +L2310: if (x != (int)x) { goto L2370; } +L2320: if (x > 10) { goto L2370; } +L2330: if (x < 1) { goto L2370; } +L2340: if (y != (int)y) { goto L2370; } +L2350: if (y > 10) { goto L2370; } +L2360: if (y >= 1) { goto L2390; } L2370: _io.WriteLine("ILLEGAL, ENTER AGAIN."); L2380: goto L2300; -L2390: if (AA[(int)X,(int)Y]>10) +L2390: if (computerGrid[(int)x,(int)y]>10) { - _io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {AA[(int)X,(int)Y]-10}"); + _io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {computerGrid[(int)x,(int)y]-10}"); goto L2300; } -L2400: CA[W]=(int)X; -L2410: DA[W]=(int)Y; +L2400: shotsX[i]=(int)x; +L2410: shotsY[i]=(int)y; } -L2460: for (var W = 1; W <= A; W++) +L2460: for (var W = 1; W <= maxShotCount; W++) { -L2470: if (AA[CA[W],DA[W]] == 3) +L2470: if (computerGrid[shotsX[W],shotsY[W]] == 3) { _io.WriteLine("YOU HIT MY BATTLESHIP."); goto L2510; } -L2480: if (AA[CA[W],DA[W]] == 2) +L2480: if (computerGrid[shotsX[W],shotsY[W]] == 2) { _io.WriteLine("YOU HIT MY CRUISER."); goto L2510; } -L2490: if (AA[CA[W],DA[W]] == 1) +L2490: if (computerGrid[shotsX[W],shotsY[W]] == 1) { _io.WriteLine("YOU HIT MY DESTROYER."); goto L2510; } -L2500: if (AA[CA[W],DA[W]] == .5F) +L2500: if (computerGrid[shotsX[W],shotsY[W]] == .5F) { _io.WriteLine("YOU HIT MY DESTROYER."); goto L2510; } -L2510: AA[CA[W],DA[W]] = 10+C; +L2510: computerGrid[shotsX[W],shotsY[W]] = 10+turnNumber; } -L2620: A = 0; -L2630: if (JS == "YES") { goto L2670; } -L2640: C = C + 1; +L2620: maxShotCount = 0; +L2630: if (startResponse == "YES") { goto L2670; } +L2640: turnNumber = turnNumber + 1; L2650: _io.WriteLine(); -L2660: _io.WriteLine($"TURN {C}"); -L2670: A = 0; -L2680: for (var W = .5F; W <= 3; W += .5F) +L2660: _io.WriteLine($"TURN {turnNumber}"); +L2670: maxShotCount = 0; +L2680: for (var shipValue = .5F; shipValue <= 3; shipValue += .5F) { -L2690: for (var X = 1; X <= 10; X++) +L2690: for (var x = 1; x <= 10; x++) { -L2700: for (var Y = 1; Y <= 10; Y++) +L2700: for (var y = 1; y <= 10; y++) { -L2710: if (AA[X,Y] == W) { goto L2750; } +L2710: if (computerGrid[x,y] == shipValue) { goto L2750; } } } L2740: continue; -L2750: A = A + (int)(W+.5F); +L2750: maxShotCount = maxShotCount + (int)(shipValue+.5F); } -L2770: P3=0; -L2780: for (var X = 1; X <= 10; X++) +L2770: untriedSquareCount=0; +L2780: for (var x = 1; x <= 10; x++) { -L2790: for (var Y = 1; Y <= 10; Y++) +L2790: for (var y = 1; y <= 10; y++) { -L2800: if (AA[X,Y]>10) { continue; } -L2810: P3=P3+1; +L2800: if (computerGrid[x,y]>10) { continue; } +L2810: untriedSquareCount=untriedSquareCount+1; } } -L2840: _io.WriteLine($"I HAVE {A} SHOTS."); -L2850: if (P3>A) { goto L2880; } +L2840: _io.WriteLine($"I HAVE {maxShotCount} SHOTS."); +L2850: if (untriedSquareCount>maxShotCount) { goto L2880; } L2860: _io.WriteLine("I HAVE MORE SHOTS THAN BLANK SQUARES."); L2870: goto L2270; -L2880: if (A != 0) { goto L2960; } +L2880: if (maxShotCount != 0) { goto L2960; } L2890: _io.WriteLine("YOU HAVE WON."); L2900: return; (int, int, int, int) L2910() { - var X = _random.Next(1, 11); - var Y = _random.Next(1, 11); - var V = _random.Next(-1, 2); - var V2 = _random.Next(-1, 2); + var startX = _random.Next(1, 11); + var startY = _random.Next(1, 11); + var deltaY = _random.Next(-1, 2); + var deltaX = _random.Next(-1, 2); - return (X, Y, V, V2); + return (startX, startY, deltaY, deltaX); } -L2960: for (var W = 1; W <= 12; W++) +L2960: for (var i = 1; i <= 12; i++) { -L2970: if (HA[W]>0) { goto L3800; } + // if damaged ships +L2970: if (hitShipValue[i]>0) { goto L3800; } } -L3000: var w=0; -L3010: var R3=0; -L3020: var (x, y, v, v2) = L2910(); +L3000: var shotCount=0; +L3010: var shotAttempts=0; +L3020: var (shotX, shotY, _, _) = L2910(); L3030: //RESTORE var index = 0; -L3040: var R2=0; -L3050: R3=R3+1; -L3060: if (R3>100) { goto L3010; } -L3070: if (x>10) { goto L3110; } -L3080: if (x>0) { goto L3120; } -L3090: x = 1 + (int)(_random.NextFloat() * 2.5F); +L3040: var strategyNumber=0; +L3050: shotAttempts=shotAttempts+1; +L3060: if (shotAttempts>100) { goto L3010; } + // ensure shot is in range +L3070: if (shotX>10) { goto L3110; } +L3080: if (shotX>0) { goto L3120; } +L3090: shotX = 1 + (int)_random.NextFloat(2.5F); L3100: goto L3120; -L3110: x = 10 - (int)(_random.NextFloat() * 2.5F); -L3120: if (y>10) { goto L3160; } -L3130: if (y>0) { goto L3270; } -L3140: y=1+(int)(_random.NextFloat() * 2.5F); +L3110: shotX = 10 - (int)_random.NextFloat(2.5F); +L3120: if (shotY>10) { goto L3160; } +L3130: if (shotY>0) { goto L3270; } +L3140: shotY=1+(int)_random.NextFloat(2.5F); L3150: goto L3270; -L3160: y=10-(int)(_random.NextFloat() * 2.5F); +L3160: shotY=10-(int)_random.NextFloat(2.5F); L3170: goto L3270; -L3180: FA[w]=x; -L3190: GA[w]=y; -L3200: if (w==A) { goto L3380; } -L3210: if (R2==6) { goto L3030; } + // record shot +L3180: tempX[shotCount]=shotX; +L3190: tempY[shotCount]=shotY; +L3200: if (shotCount==maxShotCount) { goto L3380; } +L3210: if (strategyNumber==6) { goto L3030; } L3240: //DATA 1,1,-1,1,1,-3,1,1,0,2,-1,1 var data = new[] { 1,1,-1,1,1,-3,1,1,0,2,-1,1 }; L3220: //READ X1,Y1 - var (X1, Y1) = (data[index++], data[index++]); -L3230: R2=R2+1; -L3250: x=x+X1; -L3260: y=y+Y1; -L3270: if (x>10) { goto L3210; } -L3280: if (x<1) { goto L3210; } -L3290: if (y>10) { goto L3210; } -L3300: if (y<1) { goto L3210; } -L3310: if (BA[x,y]>10) { goto L3210; } -L3320: for (var Q9 = 1; Q9 <= w; Q9++) + var (xOffset, yOffset) = (data[index++], data[index++]); +L3230: strategyNumber=strategyNumber+1; +L3250: shotX=shotX+xOffset; +L3260: shotY=shotY+yOffset; + // is the shot in range? +L3270: if (shotX>10) { goto L3210; } +L3280: if (shotX<1) { goto L3210; } +L3290: if (shotY>10) { goto L3210; } +L3300: if (shotY<1) { goto L3210; } + // have we fired here before +L3310: if (humanGrid[shotX,shotY]>10) { goto L3210; } + // have we already selected this shot? +L3320: for (var i = 1; i <= shotCount; i++) { -L3330: if (FA[Q9] != x) { continue; } -L3340: if (GA[Q9] == y) { goto L3210; } +L3330: if (tempX[i] != shotX) { continue; } +L3340: if (tempY[i] == shotY) { goto L3210; } } -L3360: w=w+1; +L3360: shotCount=shotCount+1; L3370: goto L3180; -L3380: if (KS != "YES") { goto L3420; } -L3390: for (var Z5 = 1; Z5 <= A; Z5++) + // display shots +L3380: if (seeShotsResponse != "YES") { goto L3420; } +L3390: for (var i = 1; i <= maxShotCount; i++) { -L3400: _io.WriteLine($" {FA[Z5]} {GA[Z5]}"); +L3400: _io.WriteLine($" {tempX[i]} {tempY[i]}"); } -L3420: for (var W = 1; W <= A; W++) +L3420: for (var i = 1; i <= maxShotCount; i++) { -L3430: if (BA[FA[W],GA[W]] == 3) +L3430: if (humanGrid[tempX[i],tempY[i]] == 3) { _io.WriteLine("I HIT YOUR BATTLESHIP"); } - else if (BA[FA[W],GA[W]] == 2) + else if (humanGrid[tempX[i],tempY[i]] == 2) { _io.WriteLine("I HIT YOUR CRUISER"); } - else if (BA[FA[W],GA[W]] == 1) + else if (humanGrid[tempX[i],tempY[i]] == 1) { _io.WriteLine("I HIT YOUR DESTROYER"); } - else if (BA[FA[W],GA[W]] == .5F) + else if (humanGrid[tempX[i],tempY[i]] == .5F) { _io.WriteLine("I HIT YOUR DESTROYER"); } else { - BA[FA[W],GA[W]]=10+C; + humanGrid[tempX[i],tempY[i]]=10+turnNumber; continue; } -L3570: for (var Q = 1; Q <= 12; Q++) +L3570: for (var j = 1; j <= 12; j++) { -L3580: if (EA[Q] != -1) { continue; } -L3590: EA[Q]=10+C; -L3600: HA[Q]=BA[FA[W],GA[W]]; -L3610: var M3=0; -L3620: for (var M2 = 1; M2 <= 12; M2++) + // record hit +L3580: if (hitTurnRecord[j] != -1) { continue; } +L3590: hitTurnRecord[j]=10+turnNumber; +L3600: hitShipValue[j]=humanGrid[tempX[i],tempY[i]]; + // look for past hits on same ship +L3610: var shipHits=0; +L3620: for (var k = 1; k <= 12; k++) { -L3630: if (HA[M2] != HA[Q]) { continue; } -L3640: M3=M3+1; +L3630: if (hitShipValue[k] != hitShipValue[j]) { continue; } +L3640: shipHits=shipHits+1; } -L3660: if (M3 != (int)(HA[Q]+.5F)+1+(int)(HA[Q]+.5F)/3) { goto L3470; } -L3670: for (var M2 = 1; M2 <= 12; M2++) + // if ship is not sunk +L3660: if (shipHits != (int)(hitShipValue[j]+.5F)+1+(int)(hitShipValue[j]+.5F)/3) { goto L3470; } + // otherwise, remove ship hit records +L3670: for (var k = 1; k <= 12; k++) { -L3680: if (HA[M2] != HA[Q]) { continue; } -L3690: EA[M2] = -1; -L3700: HA[M2] = -1; +L3680: if (hitShipValue[k] != hitShipValue[j]) { continue; } +L3690: hitTurnRecord[k] = -1; +L3700: hitShipValue[k] = -1; } L3720: goto L3470; } + // we shouldn't get here L3740: _io.WriteLine("PROGRAM ABORT:"); -L3750: for (var Q = 1; Q <= 12; Q++) +L3750: for (var j = 1; j <= 12; j++) { -L3760: _io.WriteLine($"EA[{Q}] = {EA[Q]}"); -L3770: _io.WriteLine($"HA[{Q}] = {HA[Q]}"); +L3760: _io.WriteLine($"{nameof(hitTurnRecord)}( {j} ) = {hitTurnRecord[j]}"); +L3770: _io.WriteLine($"{nameof(hitShipValue)}( {j} ) = {hitShipValue[j]}"); } return; -L3470: BA[FA[W],GA[W]]=10+C; +L3470: humanGrid[tempX[i],tempY[i]]=10+turnNumber; } L3490: goto L1950; L3800: //REM************************USINGEARRAY -L3810: for (var R = 1; R <= 10; R++) +L3810: for (var x = 1; x <= 10; x++) { -L3820: for (var S = 1; S <= 10; S++) +L3820: for (var y = 1; y <= 10; y++) { - KA[R,S]=0; + temp[x,y]=0; } } -L3860: for (var U = 1; U <= 12; U++) +L3860: for (var i = 1; i <= 12; i++) { -L3870: if (EA[U]<10) { continue; } -L3880: for (var R = 1; R <= 10; R++) +L3870: if (hitTurnRecord[i]<10) { continue; } +L3880: for (var x = 1; x <= 10; x++) { -L3890: for (var S = 1; S <= 10; S++) +L3890: for (var y = 1; y <= 10; y++) { -L3900: if (BA[R,S]<10) { goto L3930; } -L3910: KA[R,S]=-10000000; +L3900: if (humanGrid[x,y]<10) { goto L3930; } +L3910: temp[x,y]=-10000000; L3920: continue; -L3930: for (var M = Math.Sign(1-R); M <= Math.Sign(10-R); M++) +L3930: for (var dX = Math.Sign(1-x); dX <= Math.Sign(10-x); dX++) { -L3940: for (var N = Math.Sign(1-S); N <= Math.Sign(10-S); N++) +L3940: for (var dY = Math.Sign(1-y); dY <= Math.Sign(10-y); dY++) { -L3950: if (N+M+N*M==0) { continue; } -L3960: if (BA[R+M,S+N] != EA[U]) { continue; } -L3970: KA[R,S]=KA[R,S]+EA[U]-S*(int)(HA[U]+.5F); +L3950: if (dY+dX+dY*dX==0) { continue; } +L3960: if (humanGrid[x+dX,y+dY] != hitTurnRecord[i]) { continue; } +L3970: temp[x,y]=temp[x,y]+hitTurnRecord[i]-y*(int)(hitShipValue[i]+.5F); } } } } } -L4030: for (var R = 1; R <= A; R++) +L4030: for (var i = 1; i <= maxShotCount; i++) { -L4040: FA[R]=R; -L4050: GA[R]=R; +L4040: tempX[i]=i; +L4050: tempY[i]=i; } -L4070: for (var R = 1; R <= 10; R++) +L4070: for (var x = 1; x <= 10; x++) { -L4080: for (var S = 1; S <= 10; S++) +L4080: for (var y = 1; y <= 10; y++) { L4090: var Q9=1; -L4100: for (var M = 1; M <= A; M++) +L4100: for (var i = 1; i <= maxShotCount; i++) { -L4110: if (KA[FA[M],GA[M]]>=KA[FA[Q9],GA[Q9]]) { continue; } -L4120: Q9=M; +L4110: if (temp[tempX[i],tempY[i]]>=temp[tempX[Q9],tempY[Q9]]) { continue; } +L4120: Q9=i; } -L4131: if (R>A) { goto L4140; } -L4132: if (R==S) { goto L4210; } -L4140: if (KA[R,S]maxShotCount) { goto L4140; } +L4132: if (x==y) { goto L4210; } +L4140: if (temp[x,y] Date: Sat, 4 Mar 2023 21:35:00 +1300 Subject: [PATCH 081/141] update to tasks --- 84_Super_Star_Trek/rust/tasks.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 2ddea3df..7d31efd7 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -39,8 +39,9 @@ Started after movement and display of stats was finished (no energy management o - [x] restarting the game - [x] after defeat - [x] and by resigning -- [ ] time progression - - check all areas where time should move, and adjust accordingly +- [x] time progression + - [x] check all areas where time should move, and adjust accordingly + - looks to only be on nav and repair - [x] defeat due to time expired - [ ] intro instructions - [x] victory From 217e76071b11717ac0ad6a8b01c9659678480339 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sat, 4 Mar 2023 21:36:29 +1300 Subject: [PATCH 082/141] added pre-game text blobs --- 84_Super_Star_Trek/rust/src/view.rs | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index f7bac4db..a838922b 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -1,6 +1,7 @@ use crate::model::{Galaxy, Pos, SectorStatus, Enterprise, systems}; pub mod prompts { + pub const INSTRUCTIONS: &str = "Do you need instructions"; pub const COURSE: &str = "Course (1-9)?"; pub const TORPEDO_COURSE: &str = "Photon torpedo course (1-9)?"; pub const SHIELDS: &str = "Number of units to shields"; @@ -15,6 +16,38 @@ pub mod prompts { } } +pub fn title() { + println!(" + + + + + + + + + + + + ************************************* + * * + * * + * * * SUPER STAR TREK * * * + * * + * * + ************************************* + + + + + + + + + + "); +} + pub fn full_instructions() { println!( " INSTRUCTIONS FOR 'SUPER STAR TREK' From 15cf7b31ff26d7dc32da696d85efb5d41fc39678 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sun, 5 Mar 2023 06:33:59 +1300 Subject: [PATCH 083/141] implemented input instructions --- 84_Super_Star_Trek/rust/src/main.rs | 8 +++++++- 84_Super_Star_Trek/rust/src/view.rs | 4 +++- 84_Super_Star_Trek/rust/tasks.md | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index 78e59531..e51b179e 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -1,6 +1,6 @@ use std::process::exit; -use input::prompt; +use input::{prompt, prompt_yes_no}; use model::{Galaxy, systems}; mod input; @@ -12,6 +12,12 @@ fn main() { ctrlc::set_handler(move || { exit(0) }) .expect("Error setting Ctrl-C handler"); + view::title(); + if prompt_yes_no(view::prompts::INSTRUCTIONS) { + view::full_instructions(); + let _ = input::prompt(view::prompts::WHEN_READY); + } + let mut galaxy = Galaxy::generate_new(); let initial_klingons = galaxy.remaining_klingons(); let initial_stardate = galaxy.stardate; diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index a838922b..22f397ef 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -154,7 +154,9 @@ pub fn full_instructions() { direction/distance calculations. Option 5 = Galactic Region Name Map This option prints the names of the sixteen major - galactic regions referred to in the game.") + galactic regions referred to in the game. + +") } pub fn enterprise() { diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 7d31efd7..20f9a5c1 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -43,5 +43,5 @@ Started after movement and display of stats was finished (no energy management o - [x] check all areas where time should move, and adjust accordingly - looks to only be on nav and repair - [x] defeat due to time expired -- [ ] intro instructions +- [x] intro instructions - [x] victory From c4d4f820ac72a42d903d90bc33f3e29b16befd08 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sun, 5 Mar 2023 07:49:25 +1300 Subject: [PATCH 084/141] work on some computer functions --- 84_Super_Star_Trek/rust/src/commands.rs | 16 ++++++++++++++++ 84_Super_Star_Trek/rust/src/view.rs | 8 ++++++++ 2 files changed, 24 insertions(+) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index cc79dffb..cf427310 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -307,11 +307,27 @@ pub fn access_computer(galaxy: &Galaxy, provided: Vec) { match operation { 0 => view::galaxy_scanned_map(galaxy), + 3 => show_starbase_data(galaxy), 5 => view::galaxy_region_map(), _ => todo!() // todo implement others } } +fn show_starbase_data(galaxy: &Galaxy) { + let quadrant = &galaxy.quadrants[galaxy.enterprise.quadrant.as_index()]; + match &quadrant.star_base { + None => { + view::no_local_starbase(); + return; + }, + Some(s) => { + view::starbase_report(); + let pos = s.sector; + // calulcate direction and distance then print + } + } +} + pub fn get_power_and_fire_phasers(galaxy: &mut Galaxy, provided: Vec) { if galaxy.enterprise.damaged.contains_key(systems::PHASERS) { view::inoperable(&systems::name_for(systems::PHASERS)); diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 22f397ef..75ad1924 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -579,4 +579,12 @@ court martial!") That does it, Captain!! You are hereby relieved of command and sentenced to 99 stardates at hard labor on Cygnus 12!!") } +} + +pub fn no_local_starbase() { + println!("Mr. Spock reports, 'Sensors show no starbases in this quadrant.'") +} + +pub fn starbase_report() { + println!("From Enterprise to Starbase:'") } \ No newline at end of file From 5542e2fe59c7fa4837d65c31c54dd8988acb279b Mon Sep 17 00:00:00 2001 From: Christopher Date: Sun, 5 Mar 2023 08:11:24 +1300 Subject: [PATCH 085/141] first cut of dir / dist to local objects also bug fix for param reading, where the param wasnt bound by min max --- 84_Super_Star_Trek/rust/src/commands.rs | 5 +++-- 84_Super_Star_Trek/rust/src/input.rs | 11 ++++++---- 84_Super_Star_Trek/rust/src/model.rs | 27 +++++++++++++++++++++++++ 84_Super_Star_Trek/rust/src/view.rs | 7 +++++++ 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index cf427310..60ffcb21 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -322,8 +322,9 @@ fn show_starbase_data(galaxy: &Galaxy) { }, Some(s) => { view::starbase_report(); - let pos = s.sector; - // calulcate direction and distance then print + let origin = galaxy.enterprise.sector; + let target = s.sector; + view::direction_distance(origin.direction(target), origin.dist(target)) } } } diff --git a/84_Super_Star_Trek/rust/src/input.rs b/84_Super_Star_Trek/rust/src/input.rs index 5bd35fb3..68ab7006 100644 --- a/84_Super_Star_Trek/rust/src/input.rs +++ b/84_Super_Star_Trek/rust/src/input.rs @@ -42,12 +42,15 @@ pub fn prompt_value(prompt_text: &str, min: T, max: T) } pub fn param_or_prompt_value(params: &Vec, param_pos: usize, prompt_text: &str, min: T, max: T) -> Option { + let mut res: Option = None; if params.len() > param_pos { match params[param_pos].parse::() { - Ok(n) => Some(n), - _ => None + Ok(n) if (n >= min && n <= max) => res = Some(n), + _ => () } - } else { - return prompt_value::(prompt_text, min, max); } + if res.is_some() { + return res; + } + return prompt_value::(prompt_text, min, max); } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 9b2f1118..dd2b8dd8 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -154,6 +154,33 @@ impl Pos { self.0.abs_diff(other.0) + self.1.abs_diff(other.1) } + pub fn dist(&self, other: Pos) -> f32 { + let dx = other.0 as f32 - self.0 as f32; + let dy = other.1 as f32 - self.1 as f32; + (f32::powi(dx, 2) + f32::powi(dy, 2)).sqrt() + } + + pub fn direction(&self, other: Pos) -> f32 { + // this is a replication of the original BASIC code + let dx = other.0 as f32 - self.0 as f32; + let dy = other.1 as f32 - self.1 as f32; + let dx_dominant = dx.abs() > dy.abs(); + + let frac = if dx_dominant { dy / dx } else { -dx / dy }; + let nearest_cardinal = + if dx_dominant { + if dx > 0. { 7. } else { 3. } + } else { + if dy > 0. { 1. } else { 5. } + }; + + let mut dir = nearest_cardinal + frac; + if dir < 1. { + dir += 8. + } + dir + } + pub fn as_galactic_sector(&self, containing_quadrant: Pos) -> Self { Pos(containing_quadrant.0 * 8 + self.0, containing_quadrant.1 * 8 + self.1) } diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 75ad1924..19cc7a57 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -587,4 +587,11 @@ pub fn no_local_starbase() { pub fn starbase_report() { println!("From Enterprise to Starbase:'") +} + +pub fn direction_distance(dir: f32, dist: f32) { + println!( +"Direction = {dir} +Distance = {dist}" + ) } \ No newline at end of file From 2dba25c4eef4d4ad1563b8aa1192221e53b5a500 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sun, 5 Mar 2023 11:05:43 +1300 Subject: [PATCH 086/141] fixed calculation for direction --- 84_Super_Star_Trek/rust/src/model.rs | 9 +++++---- 84_Super_Star_Trek/rust/src/view.rs | 6 +++--- 84_Super_Star_Trek/rust/tasks.md | 1 + 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index dd2b8dd8..1bb5ef8e 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -154,16 +154,17 @@ impl Pos { self.0.abs_diff(other.0) + self.1.abs_diff(other.1) } - pub fn dist(&self, other: Pos) -> f32 { + pub fn dist(&self, other: Pos) -> u8 { let dx = other.0 as f32 - self.0 as f32; let dy = other.1 as f32 - self.1 as f32; - (f32::powi(dx, 2) + f32::powi(dy, 2)).sqrt() + (f32::powi(dx, 2) + f32::powi(dy, 2)).sqrt() as u8 } pub fn direction(&self, other: Pos) -> f32 { // this is a replication of the original BASIC code - let dx = other.0 as f32 - self.0 as f32; - let dy = other.1 as f32 - self.1 as f32; + let dy = other.0 as f32 - self.0 as f32; + let dx = other.1 as f32 - self.1 as f32; + // note i actually use x,y, but the calculation assumes y,x so they're flipped above let dx_dominant = dx.abs() > dy.abs(); let frac = if dx_dominant { dy / dx } else { -dx / dy }; diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 19cc7a57..00598b58 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -433,7 +433,7 @@ pub fn long_range_scan(galaxy: &Galaxy) -> Vec { stars = format!("{}", quadrant.stars.len()); } - print!(": {}{}{} ", klingons, stars, star_bases) + print!(": {}{}{} ", klingons, star_bases, stars) } println!(":"); println!("{:-^19}", ""); @@ -482,7 +482,7 @@ pub fn galaxy_scanned_map(galaxy: &Galaxy) { let pos = Pos(x, y); if galaxy.scanned.contains(&pos) { let quadrant = &galaxy.quadrants[pos.as_index()]; - print!(" {}{}{} ", quadrant.klingons.len(), quadrant.stars.len(), quadrant.star_base.as_ref().map_or("0", |_| "1")) + print!(" {}{}{} ", quadrant.klingons.len(), quadrant.star_base.as_ref().map_or("0", |_| "1"), quadrant.stars.len()) } else { print!(" *** "); } @@ -589,7 +589,7 @@ pub fn starbase_report() { println!("From Enterprise to Starbase:'") } -pub fn direction_distance(dir: f32, dist: f32) { +pub fn direction_distance(dir: f32, dist: u8) { println!( "Direction = {dir} Distance = {dist}" diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 20f9a5c1..f3350bd6 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -45,3 +45,4 @@ Started after movement and display of stats was finished (no energy management o - [x] defeat due to time expired - [x] intro instructions - [x] victory +- [ ] switch from x,y to y,x \ No newline at end of file From e593bfd39aa817a921480aa32d9ec35f0c8041a1 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sun, 5 Mar 2023 11:59:16 +1300 Subject: [PATCH 087/141] general status from computer --- 84_Super_Star_Trek/rust/src/commands.rs | 19 ++++++++++++------- 84_Super_Star_Trek/rust/src/main.rs | 5 ++++- 84_Super_Star_Trek/rust/src/view.rs | 16 ++++++++++++++++ 84_Super_Star_Trek/rust/tasks.md | 4 ++-- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 60ffcb21..cfbc77e1 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -248,16 +248,17 @@ fn klingons_fire(galaxy: &mut Galaxy) { } } -pub fn run_damage_control(galaxy: &mut Galaxy) { - - let ship = &mut galaxy.enterprise; - - if ship.damaged.contains_key(systems::DAMAGE_CONTROL) { +pub fn run_damage_control(galaxy: &Galaxy) { + if galaxy.enterprise.damaged.contains_key(systems::DAMAGE_CONTROL) { view::inoperable(&systems::name_for(systems::DAMAGE_CONTROL)); - } else { - view::damage_control(&ship); + return; } + + view::damage_control(&galaxy.enterprise); +} +pub fn try_starbase_ship_repair(galaxy: &mut Galaxy) { + let ship = &mut galaxy.enterprise; let quadrant = &galaxy.quadrants[ship.quadrant.as_index()]; if ship.damaged.len() == 0 || !quadrant.docked_at_starbase(ship.sector) { return; @@ -307,6 +308,10 @@ pub fn access_computer(galaxy: &Galaxy, provided: Vec) { match operation { 0 => view::galaxy_scanned_map(galaxy), + 1 => { + view::status_report(galaxy); + run_damage_control(galaxy); + }, 3 => show_starbase_data(galaxy), 5 => view::galaxy_region_map(), _ => todo!() // todo implement others diff --git a/84_Super_Star_Trek/rust/src/main.rs b/84_Super_Star_Trek/rust/src/main.rs index e51b179e..4c1a277e 100644 --- a/84_Super_Star_Trek/rust/src/main.rs +++ b/84_Super_Star_Trek/rust/src/main.rs @@ -38,7 +38,10 @@ fn main() { systems::SHORT_RANGE_SCAN => commands::perform_short_range_scan(&galaxy), systems::WARP_ENGINES => commands::gather_dir_and_speed_then_move(&mut galaxy, command[1..].into()), systems::SHIELD_CONTROL => commands::get_amount_and_set_shields(&mut galaxy, command[1..].into()), - systems::DAMAGE_CONTROL => commands::run_damage_control(&mut galaxy), + systems::DAMAGE_CONTROL => { + commands::run_damage_control(&galaxy); + commands::try_starbase_ship_repair(&mut galaxy); + } systems::LONG_RANGE_SCAN => commands::perform_long_range_scan(&mut galaxy), systems::COMPUTER => commands::access_computer(&galaxy, command[1..].into()), systems::PHASERS => commands::get_power_and_fire_phasers(&mut galaxy, command[1..].into()), diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 00598b58..34568bf3 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -594,4 +594,20 @@ pub fn direction_distance(dir: f32, dist: u8) { "Direction = {dir} Distance = {dist}" ) +} + +pub fn status_report(galaxy: &Galaxy) { + let klingon_count = galaxy.remaining_klingons(); + let star_bases = galaxy.remaining_starbases(); + let time_remaining = galaxy.final_stardate - galaxy.stardate; + let mut plural_starbase = ""; + if star_bases > 1 { + plural_starbase = "s"; + } + + println!(" Status report: +Klingons left: {klingon_count} +Mission must be completed in {time_remaining} stardates. +The Federation is maintaining {star_bases} starbase{plural_starbase} in the galaxy. +") } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index f3350bd6..9fcd937d 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -30,9 +30,9 @@ Started after movement and display of stats was finished (no energy management o - [x] torpedoes - [ ] computer - [x] 0 - output of all short and long range scans (requires tracking if a system has been scanned) - - [ ] 1 - klingons, starbases, stardate and damage control + - [x] 1 - klingons, starbases, stardate and damage control - [ ] 2 - photon torpedo data: direction and distance to all local klingons - - [ ] 3 - starbase distance and dir locally + - [x] 3 - starbase distance and dir locally - [ ] 4 - direction/distance calculator (useful for nav actions I guess) - [x] 5 - galactic name map From 50a4ddbcdc382c4c694b52e26128dfc7d2ccd737 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sun, 5 Mar 2023 14:02:39 +1300 Subject: [PATCH 088/141] direction distance calculator --- 84_Super_Star_Trek/rust/src/commands.rs | 21 ++++++++++++++++++++- 84_Super_Star_Trek/rust/src/input.rs | 19 ++++++++++++++++++- 84_Super_Star_Trek/rust/src/view.rs | 10 ++++++++++ 84_Super_Star_Trek/rust/tasks.md | 2 +- 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index cfbc77e1..2848b039 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -1,6 +1,6 @@ use rand::Rng; -use crate::{model::*, view, input::{self, param_or_prompt_value}}; +use crate::{model::*, view, input::{self, param_or_prompt_value, prompt_two_values}}; pub fn perform_short_range_scan(galaxy: &Galaxy) { if galaxy.enterprise.damaged.contains_key(systems::SHORT_RANGE_SCAN) { @@ -313,6 +313,7 @@ pub fn access_computer(galaxy: &Galaxy, provided: Vec) { run_damage_control(galaxy); }, 3 => show_starbase_data(galaxy), + 4 => direction_dist_calculator(galaxy), 5 => view::galaxy_region_map(), _ => todo!() // todo implement others } @@ -334,6 +335,24 @@ fn show_starbase_data(galaxy: &Galaxy) { } } +fn direction_dist_calculator(galaxy: &Galaxy) { + view::direction_dist_intro(&galaxy.enterprise); + loop { + let coords1 = prompt_two_values(view::prompts::INITIAL_COORDS, 1, 8).map(|(x, y)| Pos(x, y)); + if coords1.is_none() { + continue; + } + let coords2 = prompt_two_values(view::prompts::TARGET_COORDS, 1, 8).map(|(x, y)| Pos(x, y)); + if coords2.is_none() { + continue; + } + let dir = coords1.unwrap().direction(coords2.unwrap()); + let dist = coords1.unwrap().dist(coords2.unwrap()); + view::direction_distance(dir, dist); + break; + } +} + pub fn get_power_and_fire_phasers(galaxy: &mut Galaxy, provided: Vec) { if galaxy.enterprise.damaged.contains_key(systems::PHASERS) { view::inoperable(&systems::name_for(systems::PHASERS)); diff --git a/84_Super_Star_Trek/rust/src/input.rs b/84_Super_Star_Trek/rust/src/input.rs index 68ab7006..62d4daee 100644 --- a/84_Super_Star_Trek/rust/src/input.rs +++ b/84_Super_Star_Trek/rust/src/input.rs @@ -9,7 +9,7 @@ pub fn prompt(prompt_text: &str) -> Vec { let mut buffer = String::new(); if let Ok(_) = stdin.read_line(&mut buffer) { - return buffer.trim_end().split(" ").map(|s| s.to_string()).collect(); + return buffer.trim_end().split([' ', ',']).map(|s| s.to_string()).collect(); } Vec::new() } @@ -53,4 +53,21 @@ pub fn param_or_prompt_value(params: &Vec, para return res; } return prompt_value::(prompt_text, min, max); +} + +pub fn prompt_two_values(prompt_text: &str, min: T, max: T) -> Option<(T, T)> { + let passed = prompt(prompt_text); + if passed.len() != 2 { + return None + } + match passed[0].parse::() { + Ok(n1) if (n1 >= min && n1 <= max) => { + match passed[1].parse::() { + Ok(n2) if (n2 >= min && n2 <= max) => + Some((n1, n2)), + _ => None + } + } + _ => None + } } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 34568bf3..282e6dc4 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -10,6 +10,8 @@ pub mod prompts { pub const PHASERS: &str = "Number of units to fire"; pub const WHEN_READY: &str = "Press Enter when ready to accept command"; pub const COMMAND: &str = "Command?"; + pub const INITIAL_COORDS: &str = " Initial coordinates (X/Y)"; + pub const TARGET_COORDS: &str = " Final coordinates (X/Y)"; pub fn warp_factor(max_warp: f32) -> String { format!("Warp Factor (0-{})?", max_warp) @@ -610,4 +612,12 @@ Klingons left: {klingon_count} Mission must be completed in {time_remaining} stardates. The Federation is maintaining {star_bases} starbase{plural_starbase} in the galaxy. ") +} + +pub fn direction_dist_intro(enterprise: &Enterprise) { + let quadrant = enterprise.quadrant; + let sector = enterprise.sector; + println!("Direction/distance calculator: +You are at quadrant {quadrant} sector {sector} +Please enter") } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 9fcd937d..1871b317 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -33,7 +33,7 @@ Started after movement and display of stats was finished (no energy management o - [x] 1 - klingons, starbases, stardate and damage control - [ ] 2 - photon torpedo data: direction and distance to all local klingons - [x] 3 - starbase distance and dir locally - - [ ] 4 - direction/distance calculator (useful for nav actions I guess) + - [x] 4 - direction/distance calculator (useful for nav actions I guess) - [x] 5 - galactic name map - [x] restarting the game From d973dec62d26e0688dd548ad32d0feb7c0f57440 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sun, 5 Mar 2023 14:09:28 +1300 Subject: [PATCH 089/141] implemented klongon data, final computer program --- 84_Super_Star_Trek/rust/src/commands.rs | 20 ++++++++++++++++++-- 84_Super_Star_Trek/rust/src/view.rs | 8 ++++++++ 84_Super_Star_Trek/rust/tasks.md | 4 ++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 2848b039..2bc28da8 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -312,14 +312,30 @@ pub fn access_computer(galaxy: &Galaxy, provided: Vec) { view::status_report(galaxy); run_damage_control(galaxy); }, - 3 => show_starbase_data(galaxy), + 2 => show_klingon_direction_data(galaxy), + 3 => show_starbase_direction_data(galaxy), 4 => direction_dist_calculator(galaxy), 5 => view::galaxy_region_map(), _ => todo!() // todo implement others } } -fn show_starbase_data(galaxy: &Galaxy) { +fn show_klingon_direction_data(galaxy: &Galaxy) { + let quadrant = &galaxy.quadrants[galaxy.enterprise.quadrant.as_index()]; + if quadrant.klingons.len() == 0 { + view::no_local_enemies(); + return; + } + + view::klingon_report(quadrant.klingons.len() > 1); + let origin = galaxy.enterprise.sector; + for k in &quadrant.klingons { + let target = k.sector; + view::direction_distance(origin.direction(target), origin.dist(target)) + } +} + +fn show_starbase_direction_data(galaxy: &Galaxy) { let quadrant = &galaxy.quadrants[galaxy.enterprise.quadrant.as_index()]; match &quadrant.star_base { None => { diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 282e6dc4..7e8aa399 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -620,4 +620,12 @@ pub fn direction_dist_intro(enterprise: &Enterprise) { println!("Direction/distance calculator: You are at quadrant {quadrant} sector {sector} Please enter") +} + +pub fn klingon_report(more_than_one: bool) { + let mut plural = ""; + if more_than_one { + plural = "s"; + } + println!("From Enterprise to Klingon battle cruiser{}", plural) } \ No newline at end of file diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index 1871b317..c4406208 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -28,10 +28,10 @@ Started after movement and display of stats was finished (no energy management o - [x] weapons - [x] phasers - [x] torpedoes -- [ ] computer +- [x] computer - [x] 0 - output of all short and long range scans (requires tracking if a system has been scanned) - [x] 1 - klingons, starbases, stardate and damage control - - [ ] 2 - photon torpedo data: direction and distance to all local klingons + - [x] 2 - photon torpedo data: direction and distance to all local klingons - [x] 3 - starbase distance and dir locally - [x] 4 - direction/distance calculator (useful for nav actions I guess) - [x] 5 - galactic name map From b9947f69b515d675f7c0f4be65a1f264adeb0688 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sun, 5 Mar 2023 15:05:25 +1300 Subject: [PATCH 090/141] switched from x/y system to y/x (y is horizontal, x is vertical --- 84_Super_Star_Trek/rust/src/model.rs | 19 +++++++++---------- 84_Super_Star_Trek/rust/src/view.rs | 18 +++++++++--------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 1bb5ef8e..1e4359ed 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -162,9 +162,8 @@ impl Pos { pub fn direction(&self, other: Pos) -> f32 { // this is a replication of the original BASIC code - let dy = other.0 as f32 - self.0 as f32; - let dx = other.1 as f32 - self.1 as f32; - // note i actually use x,y, but the calculation assumes y,x so they're flipped above + let dx = other.0 as f32 - self.0 as f32; + let dy = other.1 as f32 - self.1 as f32; let dx_dominant = dx.abs() > dy.abs(); let frac = if dx_dominant { dy / dx } else { -dx / dy }; @@ -214,15 +213,15 @@ impl Display for Pos { } pub const COURSES : [(f32, f32); 9] = [ - (1., 0.), - (1., -1.), - (0., -1.), - (-1., -1.), - (-1., 0.), - (-1., 1.), (0., 1.), + (-1., 1.), + (-1., 0.), + (-1., -1.), + (0., -1.), + (1., -1.), + (1., 0.), (1., 1.), - (1., 0.), // course 9 is equal to course 1 + (0., 1.), // course 9 is equal to course 1 ]; #[derive(PartialEq)] diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 7e8aa399..1cc2d202 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -226,7 +226,7 @@ const SUB_REGION_NAMES: [&str; 4] = ["I", "II", "III", "IV"]; fn quadrant_name(quadrant: Pos) -> String { format!("{} {}", - REGION_NAMES[((quadrant.1 << 1) + (quadrant.0 >> 2)) as usize], + REGION_NAMES[((quadrant.0 << 1) + (quadrant.1 >> 2)) as usize], SUB_REGION_NAMES[(quadrant.1 % 4) as usize]) } @@ -264,8 +264,8 @@ pub fn short_range_scan(model: &Galaxy) { ]; println!("{:-^33}", ""); - for y in 0..=7 { - for x in 0..=7 { + for x in 0..=7 { + for y in 0..=7 { let pos = Pos(x, y); if &pos == &model.enterprise.sector { print!("<*> ") @@ -278,7 +278,7 @@ pub fn short_range_scan(model: &Galaxy) { } } } - println!("{:>9}{}", "", data[y as usize]) + println!("{:>9}{}", "", data[x as usize]) } println!("{:-^33}", ""); } @@ -419,8 +419,8 @@ pub fn long_range_scan(galaxy: &Galaxy) -> Vec { println!("Long range scan for quadrant {}", galaxy.enterprise.quadrant); println!("{:-^19}", ""); - for y in cy - 1..=cy + 1 { - for x in cx - 1..=cx + 1 { + for x in cx - 1..=cx + 1 { + for y in cy - 1..=cy + 1 { let mut klingons = "*".into(); let mut star_bases = "*".into(); let mut stars = "*".into(); @@ -478,9 +478,9 @@ pub fn galaxy_scanned_map(galaxy: &Galaxy) { "Computer record of galaxy for quadrant {} 1 2 3 4 5 6 7 8 ----- ----- ----- ----- ----- ----- ----- -----", galaxy.enterprise.quadrant); - for y in 0..8 { - print!("{} ", y+1); - for x in 0..8 { + for x in 0..8 { + print!("{} ", x+1); + for y in 0..8 { let pos = Pos(x, y); if galaxy.scanned.contains(&pos) { let quadrant = &galaxy.quadrants[pos.as_index()]; From 8c1e7257163ecdcee5512c141bf707be0d6f79c4 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sun, 5 Mar 2023 15:05:49 +1300 Subject: [PATCH 091/141] updated tasks --- 84_Super_Star_Trek/rust/tasks.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md index c4406208..da9e49c9 100644 --- a/84_Super_Star_Trek/rust/tasks.md +++ b/84_Super_Star_Trek/rust/tasks.md @@ -45,4 +45,5 @@ Started after movement and display of stats was finished (no energy management o - [x] defeat due to time expired - [x] intro instructions - [x] victory -- [ ] switch from x,y to y,x \ No newline at end of file +- [x] switch from x,y to y,x +- [ ] uppercase prompts? \ No newline at end of file From 9f4f04582610c50340c35304554ff16c8349b7b9 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sun, 5 Mar 2023 15:30:17 +1300 Subject: [PATCH 092/141] implemented collision detection --- 84_Super_Star_Trek/rust/src/commands.rs | 26 +++++++++++-- 84_Super_Star_Trek/rust/src/model.rs | 2 +- 84_Super_Star_Trek/rust/src/view.rs | 8 +++- 84_Super_Star_Trek/rust/tasks.md | 49 ------------------------- 4 files changed, 29 insertions(+), 56 deletions(-) delete mode 100644 84_Super_Star_Trek/rust/tasks.md diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 2bc28da8..38bc3508 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -40,7 +40,7 @@ pub fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy, provided: Vec let course = input::param_or_prompt_value(&provided, 0, view::prompts::COURSE, 1.0, 9.0); if course.is_none() { - view::bad_nav(); + view::bad_course_data(); return; } @@ -53,7 +53,7 @@ pub fn gather_dir_and_speed_then_move(galaxy: &mut Galaxy, provided: Vec let speed = input::param_or_prompt_value(&provided, 1, &view::prompts::warp_factor(max_warp), 0.0, 8.0); if speed.is_none() { - view::bad_nav(); + view::bad_course_data(); return; } @@ -130,9 +130,27 @@ fn move_enterprise(course: f32, warp_speed: f32, galaxy: &mut Galaxy) { let ship = &mut galaxy.enterprise; - // todo account for being blocked + let (mut path, mut hit_edge) = find_nav_path(ship.quadrant, ship.sector, course, warp_speed); + for i in 0..path.len() { + let (quadrant, sector) = path[i].to_local_quadrant_sector(); + if quadrant != ship.quadrant { + break; // have left current quadrant, so collision checks removed. if there is a collision at the dest... /shrug? + } + let quadrant = &galaxy.quadrants[quadrant.as_index()]; + if quadrant.sector_status(sector) != SectorStatus::Empty { + path = path[..i].into(); + hit_edge = false; + if i > 0 { + let (_, last_sector) = path[path.len() - 1].to_local_quadrant_sector(); + view::bad_nav(last_sector); + } else { + view::bad_nav(ship.sector); + return; + } + break; + } + } - let (path, hit_edge) = find_nav_path(ship.quadrant, ship.sector, course, warp_speed); let energy_cost = path.len() as u16 + 10; if energy_cost > ship.total_energy { diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 1e4359ed..10b97a4d 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -56,7 +56,7 @@ impl Enterprise { return; } - view::enterprise_hit(&hit_strength, §or); + view::enterprise_hit(&hit_strength, sector); if self.shields <= hit_strength { view::enterprise_destroyed(); diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index 1cc2d202..c276dee2 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -310,15 +310,19 @@ pub fn enterprise_destroyed() { println!("The Enterprise has been destroyed. The Federation will be conquered."); } -pub fn bad_nav() { +pub fn bad_course_data() { println!(" Lt. Sulu reports, 'Incorrect course data, sir!'") } +pub fn bad_nav(current_sector: Pos) { + println!("Warp engines shut down at sector {current_sector} dues to bad navigation") +} + pub fn bad_torpedo_course() { println!(" Ensign Chekov reports, 'Incorrect course data, sir!'") } -pub fn enterprise_hit(hit_strength: &u16, from_sector: &Pos) { +pub fn enterprise_hit(hit_strength: &u16, from_sector: Pos) { println!("{hit_strength} unit hit on Enterprise from sector {from_sector}"); } diff --git a/84_Super_Star_Trek/rust/tasks.md b/84_Super_Star_Trek/rust/tasks.md deleted file mode 100644 index da9e49c9..00000000 --- a/84_Super_Star_Trek/rust/tasks.md +++ /dev/null @@ -1,49 +0,0 @@ -# Tasks - -Started after movement and display of stats was finished (no energy management or collision detection or anything). - -- [x] klingon movement -- [x] klingon firing, game over etc -- [x] add intro -- [x] add entering (and starting in) sector headers -- [x] conditions and danger messages -- [x] remove energy on move -- [x] shields - - [x] shield control - - [x] shield hit absorption -- [x] subsystem damage - - [x] and support for reports -- [x] random system damage or repairs on move -- [x] lrs? -- [x] stranded... -- [ ] stop before hitting an object - - when moving across a sector, the enterprise should stop before it runs into something - - the current move is a jump, which makes this problematic. would need to rewrite it - - also, movement courses could be floats, according to the instructions, allowing for more precise movement and aiming -- [x] better command reading - support entering multiple values on a line (e.g. nav 3 0.1) -- [x] starbases - - [x] proximity detection for docking - - [x] repair on damage control - - [x] protection from shots -- [x] weapons - - [x] phasers - - [x] torpedoes -- [x] computer - - [x] 0 - output of all short and long range scans (requires tracking if a system has been scanned) - - [x] 1 - klingons, starbases, stardate and damage control - - [x] 2 - photon torpedo data: direction and distance to all local klingons - - [x] 3 - starbase distance and dir locally - - [x] 4 - direction/distance calculator (useful for nav actions I guess) - - [x] 5 - galactic name map - -- [x] restarting the game - - [x] after defeat - - [x] and by resigning -- [x] time progression - - [x] check all areas where time should move, and adjust accordingly - - looks to only be on nav and repair - - [x] defeat due to time expired -- [x] intro instructions -- [x] victory -- [x] switch from x,y to y,x -- [ ] uppercase prompts? \ No newline at end of file From c59dea8e21c9719bf5dd673765961df22b77bc81 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sun, 5 Mar 2023 15:48:53 +1300 Subject: [PATCH 093/141] removed final todo --- 84_Super_Star_Trek/rust/src/commands.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index 38bc3508..d2eefc61 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -334,7 +334,7 @@ pub fn access_computer(galaxy: &Galaxy, provided: Vec) { 3 => show_starbase_direction_data(galaxy), 4 => direction_dist_calculator(galaxy), 5 => view::galaxy_region_map(), - _ => todo!() // todo implement others + _ => () // unreachable } } From e6118b4622ad91ad0594403bbd59cdb55d437764 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sun, 5 Mar 2023 15:55:54 +1300 Subject: [PATCH 094/141] added a readme --- 84_Super_Star_Trek/rust/readme.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 84_Super_Star_Trek/rust/readme.md diff --git a/84_Super_Star_Trek/rust/readme.md b/84_Super_Star_Trek/rust/readme.md new file mode 100644 index 00000000..7889ddf3 --- /dev/null +++ b/84_Super_Star_Trek/rust/readme.md @@ -0,0 +1,10 @@ +# Super Star Trek - Rust version + +Explanation of modules: + +- main.rs - creates the galaxy (generation functions are in model.rs as impl methods) then loops listening for commands. after each command checks for victory or defeat condtions. +- model.rs - all the structs and enums that represent the galaxy. key methods in here (as impl methods) are generation functions on galaxy and quadrant, and various comparison methods on the 'Pos' tuple type. +- commands.rs - most of the code that implements instructions given by the player (some code logic is in the model impls, and some in view.rs if its view only). +- view.rs - all text printed to the output, mostly called by command.rs (like view::bad_nav for example). also contains the prompts printed to the user (e.g. view::prompts::COMMAND). +- input.rs - utility methods for getting input from the user, including logic for parsing numbers, repeating prompts until a correct value is provided etc. + From 790e36611fe90e66f7bd6ef949d909a60cbd5015 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sun, 5 Mar 2023 16:02:59 +1300 Subject: [PATCH 095/141] more detail in the readme --- 84_Super_Star_Trek/rust/readme.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/84_Super_Star_Trek/rust/readme.md b/84_Super_Star_Trek/rust/readme.md index 7889ddf3..ae2f58dc 100644 --- a/84_Super_Star_Trek/rust/readme.md +++ b/84_Super_Star_Trek/rust/readme.md @@ -2,9 +2,18 @@ Explanation of modules: -- main.rs - creates the galaxy (generation functions are in model.rs as impl methods) then loops listening for commands. after each command checks for victory or defeat condtions. -- model.rs - all the structs and enums that represent the galaxy. key methods in here (as impl methods) are generation functions on galaxy and quadrant, and various comparison methods on the 'Pos' tuple type. -- commands.rs - most of the code that implements instructions given by the player (some code logic is in the model impls, and some in view.rs if its view only). -- view.rs - all text printed to the output, mostly called by command.rs (like view::bad_nav for example). also contains the prompts printed to the user (e.g. view::prompts::COMMAND). -- input.rs - utility methods for getting input from the user, including logic for parsing numbers, repeating prompts until a correct value is provided etc. +- [main.rs](./src/main.rs) - creates the galaxy (generation functions are in model.rs as impl methods) then loops listening for commands. after each command checks for victory or defeat condtions. +- [model.rs](./src/model.rs) - all the structs and enums that represent the galaxy. key methods in here (as impl methods) are generation functions on galaxy and quadrant, and various comparison methods on the 'Pos' tuple type. +- [commands.rs](./src/commands.rs) - most of the code that implements instructions given by the player (some code logic is in the model impls, and some in view.rs if its view only). +- [view.rs](./src/view.rs) - all text printed to the output, mostly called by command.rs (like view::bad_nav for example). also contains the prompts printed to the user (e.g. view::prompts::COMMAND). +- [input.rs](./src/input.rs) - utility methods for getting input from the user, including logic for parsing numbers, repeating prompts until a correct value is provided etc. +Basically the user is asked for the next command, this runs a function that usually checks if the command system is working, and if so will gather additional input (see next note for a slight change here), then either the model is read and info printed, or its mutated in some way (e.g. firing a torpedo, which reduces the torpedo count on the enterprise and can destroy klingons and star bases; finally the klingons fire back and can destroy the enterprise). Finally the win/lose conditions are checked before the loop repeats. + +## Changes from the original + +I have tried to keep it as close as possible. Notable changes are: + +- commands can be given with parameters in line. e.g. while 'nav' will ask for course and then warp speed in the original, here you can *optionally* also do this as one line, e.g. `nav 1 0.1` to move one sector east. I'm sorry - it was driving me insane in its original form (which is still sorted, as is partial application e.g. nav 1 to preset direction and then provide speed). +- text is mostly not uppercase, as text was in the basic version. this would be easy to change however as all text is in view.rs, but I chose not to. +- the navigation system (plotting direction, paths and collision detection) is as close as I could make it to the basic version (by using other language conversions as specification sources) but I suspect is not perfect. seems to work well enough however. \ No newline at end of file From 371b72fc36733a6881782cf7670788ec3262b4f6 Mon Sep 17 00:00:00 2001 From: Christopher Date: Sun, 5 Mar 2023 16:04:42 +1300 Subject: [PATCH 096/141] small cleanup --- 84_Super_Star_Trek/rust/src/model.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/model.rs b/84_Super_Star_Trek/rust/src/model.rs index 10b97a4d..aa08d734 100644 --- a/84_Super_Star_Trek/rust/src/model.rs +++ b/84_Super_Star_Trek/rust/src/model.rs @@ -257,9 +257,9 @@ impl Galaxy { Galaxy { stardate, final_stardate: stardate + rng.gen_range(25..=35) as f32, - quadrants: quadrants, - scanned: scanned, - enterprise: enterprise + quadrants, + scanned, + enterprise } } From cfc7def425fefb1b6b41d3f2754e799dd53ba3e5 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Sun, 5 Mar 2023 21:03:40 +1100 Subject: [PATCH 097/141] Simplify some logic --- 77_Salvo/csharp/Game.cs | 320 ++++++++++++++++++++-------------------- 1 file changed, 159 insertions(+), 161 deletions(-) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index 02cab91e..3c382521 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -4,6 +4,10 @@ namespace Salvo; internal class Game { + private static readonly float[] _shipValue = new[] { 0.5F, 1, 2, 3 }; + private static readonly int[] _shipFirstIndex = new[] { 11, 9, 6, 1 }; + private static readonly int[] _shipSize = new[] { 2, 2, 3, 5 }; + private readonly IReadWrite _io; private readonly IRandom _random; @@ -17,7 +21,8 @@ internal class Game { _io.Write(Streams.Title); -L1040: var computerGrid = new float[11,11]; + +L1040: var humanGrid = new float[11,11]; var shotsX = new int[8]; var shotsY = new int[8]; @@ -25,54 +30,27 @@ L1040: var computerGrid = new float[11,11]; var tempX = new int[13]; var tempY = new int[13]; var hitShipValue = new float[13]; - var temp = new int[11,11]; -L1060: for (var W = 1; W <= 12; W++) +L1060: for (var i = 1; i <= 12; i++) { -L1070: hitTurnRecord[W] = -1; -L1080: hitShipValue[W] = -1; +L1070: hitTurnRecord[i] = -1; +L1080: hitShipValue[i] = -1; } -L1100: for (var X = 1; X <= 10; X++) - { -L1110: for (var Y = 1; Y <= 10; Y++) - { -L1120: humanGrid[X,Y] = 0; - } - } -L1150: for (var X = 1; X <= 12; X++) - { -L1160: tempX[X]=0; -L1170: tempY[X]=0; - } -L1190: for (var X = 1; X <= 10; X++) - { -L1200: for (var Y = 1; Y <= 10; Y++) - { -L1210: computerGrid[X,Y]=0; - } - } -L1240: for (var K = 4; K >= 1; K--) +L1190: var computerGrid = new float[11,11]; +L1240: for (var K = 3; K >= 0; K--) { L1250: var shipGenerationAttempts=0; -L1260: var (startX, startY, deltaY, deltaX) = L2910(); -L1270: int GetFirstIndex(int shipNumber) => (5-shipNumber)*3-2*(shipNumber/4)+Math.Sign(shipNumber-1)-1; -L1280: int GetShipSizeLessOne(int shipNumber) => shipNumber + shipNumber/4 - Math.Sign(shipNumber-1); -L1290: if (deltaY+deltaX+deltaY*deltaX == 0) { goto L1260; } -L1300: if (startY+deltaY*GetShipSizeLessOne(K)>10) { goto L1260; } -L1310: if (startY+deltaY*GetShipSizeLessOne(K)<1) { goto L1260; } -L1320: if (startX+deltaX*GetShipSizeLessOne(K)>10) { goto L1260; } -L1330: if (startX+deltaX*GetShipSizeLessOne(K)<1) { goto L1260; } -L1340: shipGenerationAttempts=shipGenerationAttempts+1; +L1260: var (startX, startY, deltaY, deltaX) = GetRandomShipCoordinatesInRange(K); +L1340: shipGenerationAttempts++; L1350: if (shipGenerationAttempts>25) { goto L1190; } // determine ship coordinates -L1360: for (var i = 0; i <= GetShipSizeLessOne(K); i++) +L1360: for (var i = 0; i <= _shipSize[K] - 1; i++) { - tempX[i+GetFirstIndex(K)]=startX+deltaX*i; -L1380: tempY[i+GetFirstIndex(K)]=startY+deltaY*i; + tempX[i+_shipFirstIndex[K]]=startX+deltaX*i; +L1380: tempY[i+_shipFirstIndex[K]]=startY+deltaY*i; } -L1400: var firstIndex=GetFirstIndex(K); -L1405: if (firstIndex>firstIndex+GetShipSizeLessOne(K)) { goto L1460; } // Can't be true +L1400: var firstIndex=_shipFirstIndex[K]; // detect proximity to previous ships - for (var i = firstIndex; i <= firstIndex+GetShipSizeLessOne(K); i++) + for (var i = firstIndex; i <= firstIndex+_shipSize[K] - 1; i++) { L1415: if (firstIndex<2) { continue; } // Only true for the Battleship L1420: for (var j = 1; j <= firstIndex-1; j++) @@ -81,60 +59,63 @@ L1430: if (Math.Sqrt((tempX[j]-tempX[i])*(tempX[j]-tempX[i]) + (tem } } // put ship on board -L1460: for (var i = 0; i <= GetShipSizeLessOne(K); i++) +L1460: for (var i = firstIndex; i <= firstIndex + _shipSize[K] - 1; i++) { -L1470: computerGrid[tempX[i+firstIndex],tempY[i+firstIndex]]=.5F+Math.Sign(K-1)*(K-1.5F); + computerGrid[tempX[i], tempY[i]] = _shipValue[K]; } } L1500: _io.WriteLine("ENTER COORDINATES FOR..."); L1510: _io.WriteLine("BATTLESHIP"); L1520: for (var i = 1; i <= 5; i++) { - var (x, y) = _io.Read2Numbers(""); -L1540: humanGrid[(int)x, (int)y] = 3; + var (x, y) = _io.ReadCoordinates(); +L1540: humanGrid[x, y] = 3; } L1560: _io.WriteLine("CRUISER"); L1570: for (var i = 1; i <= 3; i++) { - var (x, y) = _io.Read2Numbers(""); -L1590: humanGrid[(int)x, (int)y]=2; + var (x, y) = _io.ReadCoordinates(); +L1590: humanGrid[x, y] = 2; } L1610: _io.WriteLine("DESTROYER"); L1620: for (var i = 1; i <= 2; i++) { - var (x, y) = _io.Read2Numbers(""); -L1640: humanGrid[(int)x, (int)y]=1; + var (x, y) = _io.ReadCoordinates(); +L1640: humanGrid[x, y] = 1; } L1660: _io.WriteLine("DESTROYER"); L1670: for (var i = 1; i <= 2; i++) { - var (x, y) = _io.Read2Numbers(""); -L1690: humanGrid[(int)x, (int)y]=.5F; + var (x, y) = _io.ReadCoordinates(); +L1690: humanGrid[x, y] = 0.5F; } L1710: var startResponse = _io.ReadString("DO YOU WANT TO START"); -L1730: if (startResponse != "WHERE ARE YOUR SHIPS?") { goto L1890; } -L1740: _io.WriteLine("BATTLESHIP"); -L1750: for (var i = 1; i <= 5; i++) +L1730: while (startResponse == "WHERE ARE YOUR SHIPS?") { -L1760: _io.WriteLine($" {tempX[i]} {tempY[i]} "); + _io.WriteLine("BATTLESHIP"); + for (var i = 1; i <= 5; i++) + { + _io.WriteLine($" {tempX[i]} {tempY[i]} "); + } + _io.WriteLine("CRUISER"); + _io.WriteLine($" {tempX[6]} {tempY[6]} "); + _io.WriteLine($" {tempX[7]} {tempY[7]} "); + _io.WriteLine($" {tempX[8]} {tempY[8]} "); + _io.WriteLine("DESTROYER"); + _io.WriteLine($" {tempX[9]} {tempY[9]} "); + _io.WriteLine($" {tempX[10]} {tempY[10]} "); + _io.WriteLine("DESTROYER"); + _io.WriteLine($" {tempX[11]} {tempY[11]} "); + _io.WriteLine($" {tempX[12]} {tempY[12]} "); + + startResponse = _io.ReadString("DO YOU WANT TO START"); } -L1780: _io.WriteLine("CRUISER"); -L1790: _io.WriteLine($" {tempX[6]} {tempY[6]} "); -L1800: _io.WriteLine($" {tempX[7]} {tempY[7]} "); -L1810: _io.WriteLine($" {tempX[8]} {tempY[8]} "); -L1820: _io.WriteLine("DESTROYER"); -L1830: _io.WriteLine($" {tempX[9]} {tempY[9]} "); -L1840: _io.WriteLine($" {tempX[10]} {tempY[10]} "); -L1850: _io.WriteLine("DESTROYER"); -L1860: _io.WriteLine($" {tempX[11]} {tempY[11]} "); -L1870: _io.WriteLine($" {tempX[12]} {tempY[12]} "); -L1880: goto L1710; L1890: var turnNumber=0; L1900: var seeShotsResponse = _io.ReadString("DO YOU WANT TO SEE MY SHOTS"); L1920: _io.WriteLine(); L1930: if (startResponse != "YES") { goto L2620; } L1950: if (startResponse != "YES") { goto L1990; } -L1960: turnNumber=turnNumber+1; +L1960: turnNumber++; L1970: _io.WriteLine(); L1980: _io.WriteLine($"TURN {turnNumber}"); L1990: var maxShotCount=0; @@ -148,78 +129,56 @@ L2030: if (humanGrid[x,y] == shipValue) { goto L2070; } } } L2060: continue; -L2070: maxShotCount=maxShotCount+(int)(shipValue+.5F); +L2070: maxShotCount+=(int)(shipValue+.5F); } L2090: for (var i = 1; i <= 7; i++) { -L2100: shotsX[i] = 0; -L2110: shotsY[i] = 0; -L2120: tempX[i] = 0; -L2130: tempY[i] = 0; +L2100: shotsX[i] = shotsY[i] = tempX[i] = tempY[i] = 0; } L2150: var untriedSquareCount=0; L2160: for (var x = 1; x <= 10; x++) { L2170: for (var y = 1; y <= 10; y++) { -L2180: if (computerGrid[x,y]>10) { continue; } -L2190: untriedSquareCount=untriedSquareCount+1; +L2180: if (computerGrid[x,y] <= 10) { untriedSquareCount++; } } } L2220: _io.WriteLine($"YOU HAVE {maxShotCount} SHOTS."); -L2230: if (untriedSquareCount >= maxShotCount) { goto L2260; } -L2240: _io.WriteLine("YOU HAVE MORE SHOTS THAN THERE ARE BLANK SQUARES."); -L2250: goto L2890; -L2260: if (maxShotCount != 0) { goto L2290; } -L2270: _io.WriteLine("I HAVE WON."); -L2280: return; + if (maxShotCount == 0) { goto L2270; } +L2230: if (maxShotCount > untriedSquareCount) + { + _io.WriteLine("YOU HAVE MORE SHOTS THAN THERE ARE BLANK SQUARES."); +L2250: goto L2890; + } L2290: for (var i = 1; i <= maxShotCount; i++) { -L2300: var (x, y) = _io.Read2Numbers(""); -L2310: if (x != (int)x) { goto L2370; } -L2320: if (x > 10) { goto L2370; } -L2330: if (x < 1) { goto L2370; } -L2340: if (y != (int)y) { goto L2370; } -L2350: if (y > 10) { goto L2370; } -L2360: if (y >= 1) { goto L2390; } -L2370: _io.WriteLine("ILLEGAL, ENTER AGAIN."); -L2380: goto L2300; -L2390: if (computerGrid[(int)x,(int)y]>10) - { - _io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {computerGrid[(int)x,(int)y]-10}"); - goto L2300; + while (true) + { + var (x, y) = _io.ReadValidCoordinates(); +L2390: if (computerGrid[x,y]>10) + { + _io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {computerGrid[x,y]-10}"); + continue; + } + shotsX[i]=x; + shotsY[i]=y; + break; } -L2400: shotsX[i]=(int)x; -L2410: shotsY[i]=(int)y; } L2460: for (var W = 1; W <= maxShotCount; W++) { -L2470: if (computerGrid[shotsX[W],shotsY[W]] == 3) - { - _io.WriteLine("YOU HIT MY BATTLESHIP."); - goto L2510; - } -L2480: if (computerGrid[shotsX[W],shotsY[W]] == 2) - { - _io.WriteLine("YOU HIT MY CRUISER."); - goto L2510; - } -L2490: if (computerGrid[shotsX[W],shotsY[W]] == 1) - { - _io.WriteLine("YOU HIT MY DESTROYER."); - goto L2510; - } -L2500: if (computerGrid[shotsX[W],shotsY[W]] == .5F) - { - - _io.WriteLine("YOU HIT MY DESTROYER."); - goto L2510; - } + _io.WriteLine(computerGrid[shotsX[W],shotsY[W]] switch + { + 3 => "YOU HIT MY BATTLESHIP.", + 2 => "YOU HIT MY CRUISER.", + 1 => "YOU HIT MY DESTROYER.", + .5F => "YOU HIT MY DESTROYER.", + _ => throw new InvalidOperationException($"Unexpected value {computerGrid[shotsX[W],shotsY[W]]}") + }); L2510: computerGrid[shotsX[W],shotsY[W]] = 10+turnNumber; } -L2620: maxShotCount = 0; -L2630: if (startResponse == "YES") { goto L2670; } -L2640: turnNumber = turnNumber + 1; +L2620: if (startResponse == "YES") { goto L2670; } +L2640: turnNumber++; L2650: _io.WriteLine(); L2660: _io.WriteLine($"TURN {turnNumber}"); L2670: maxShotCount = 0; @@ -233,7 +192,7 @@ L2710: if (computerGrid[x,y] == shipValue) { goto L2750; } } } L2740: continue; -L2750: maxShotCount = maxShotCount + (int)(shipValue+.5F); +L2750: maxShotCount += (int)(shipValue+.5F); } L2770: untriedSquareCount=0; L2780: for (var x = 1; x <= 10; x++) @@ -241,7 +200,7 @@ L2780: for (var x = 1; x <= 10; x++) L2790: for (var y = 1; y <= 10; y++) { L2800: if (computerGrid[x,y]>10) { continue; } -L2810: untriedSquareCount=untriedSquareCount+1; +L2810: untriedSquareCount++; } } L2840: _io.WriteLine($"I HAVE {maxShotCount} SHOTS."); @@ -251,16 +210,9 @@ L2870: goto L2270; L2880: if (maxShotCount != 0) { goto L2960; } L2890: _io.WriteLine("YOU HAVE WON."); L2900: return; +L2270: _io.WriteLine("I HAVE WON."); + return; - (int, int, int, int) L2910() - { - var startX = _random.Next(1, 11); - var startY = _random.Next(1, 11); - var deltaY = _random.Next(-1, 2); - var deltaX = _random.Next(-1, 2); - - return (startX, startY, deltaY, deltaX); - } L2960: for (var i = 1; i <= 12; i++) { @@ -269,11 +221,11 @@ L2970: if (hitShipValue[i]>0) { goto L3800; } } L3000: var shotCount=0; L3010: var shotAttempts=0; -L3020: var (shotX, shotY, _, _) = L2910(); +L3020: var (shotX, shotY, _, _) = GetRandomShipCoordinates(); L3030: //RESTORE var index = 0; L3040: var strategyNumber=0; -L3050: shotAttempts=shotAttempts+1; +L3050: shotAttempts++; L3060: if (shotAttempts>100) { goto L3010; } // ensure shot is in range L3070: if (shotX>10) { goto L3110; } @@ -296,9 +248,9 @@ L3240: //DATA 1,1,-1,1,1,-3,1,1,0,2,-1,1 var data = new[] { 1,1,-1,1,1,-3,1,1,0,2,-1,1 }; L3220: //READ X1,Y1 var (xOffset, yOffset) = (data[index++], data[index++]); -L3230: strategyNumber=strategyNumber+1; -L3250: shotX=shotX+xOffset; -L3260: shotY=shotY+yOffset; +L3230: strategyNumber++; +L3250: shotX+=xOffset; +L3260: shotY+=yOffset; // is the shot in range? L3270: if (shotX>10) { goto L3210; } L3280: if (shotX<1) { goto L3210; } @@ -309,10 +261,9 @@ L3310: if (humanGrid[shotX,shotY]>10) { goto L3210; } // have we already selected this shot? L3320: for (var i = 1; i <= shotCount; i++) { -L3330: if (tempX[i] != shotX) { continue; } -L3340: if (tempY[i] == shotY) { goto L3210; } +L3330: if (tempX[i] == shotX && tempY[i] == shotY) { goto L3210; } } -L3360: shotCount=shotCount+1; +L3360: shotCount++; L3370: goto L3180; // display shots L3380: if (seeShotsResponse != "YES") { goto L3420; } @@ -354,16 +305,17 @@ L3610: var shipHits=0; L3620: for (var k = 1; k <= 12; k++) { L3630: if (hitShipValue[k] != hitShipValue[j]) { continue; } -L3640: shipHits=shipHits+1; +L3640: shipHits++; } // if ship is not sunk L3660: if (shipHits != (int)(hitShipValue[j]+.5F)+1+(int)(hitShipValue[j]+.5F)/3) { goto L3470; } // otherwise, remove ship hit records L3670: for (var k = 1; k <= 12; k++) { -L3680: if (hitShipValue[k] != hitShipValue[j]) { continue; } -L3690: hitTurnRecord[k] = -1; -L3700: hitShipValue[k] = -1; +L3680: if (hitShipValue[k] == hitShipValue[j]) + { +L3700: hitShipValue[k] = hitTurnRecord[k] = -1; + } } L3720: goto L3470; } @@ -379,13 +331,7 @@ L3470: humanGrid[tempX[i],tempY[i]]=10+turnNumber; } L3490: goto L1950; L3800: //REM************************USINGEARRAY -L3810: for (var x = 1; x <= 10; x++) - { -L3820: for (var y = 1; y <= 10; y++) - { - temp[x,y]=0; - } - } + var temp = new int[11,11]; L3860: for (var i = 1; i <= 12; i++) { L3870: if (hitTurnRecord[i]<10) { continue; } @@ -393,18 +339,24 @@ L3880: for (var x = 1; x <= 10; x++) { L3890: for (var y = 1; y <= 10; y++) { -L3900: if (humanGrid[x,y]<10) { goto L3930; } -L3910: temp[x,y]=-10000000; -L3920: continue; -L3930: for (var dX = Math.Sign(1-x); dX <= Math.Sign(10-x); dX++) - { -L3940: for (var dY = Math.Sign(1-y); dY <= Math.Sign(10-y); dY++) +L3900: if (humanGrid[x,y]>=10) + { +L3930: for (var dX = Math.Sign(1-x); dX <= Math.Sign(10-x); dX++) { -L3950: if (dY+dX+dY*dX==0) { continue; } -L3960: if (humanGrid[x+dX,y+dY] != hitTurnRecord[i]) { continue; } -L3970: temp[x,y]=temp[x,y]+hitTurnRecord[i]-y*(int)(hitShipValue[i]+.5F); +L3940: for (var dY = Math.Sign(1-y); dY <= Math.Sign(10-y); dY++) + { +L3950: if (dX == 0 && dY ==0) { continue; } +L3960: if (humanGrid[x+dX,y+dY] == hitTurnRecord[i]) + { +L3970: temp[x,y]=temp[x,y]+hitTurnRecord[i]-y*(int)(hitShipValue[i]+.5F); + } + } } } + else + { +L3910: temp[x,y]=-10000000; + } } } } @@ -426,10 +378,10 @@ L4120: Q9=i; L4131: if (x>maxShotCount) { goto L4140; } L4132: if (x==y) { goto L4210; } L4140: if (temp[x,y]=1 && endX<=10 && endY>=1 && endY<=10) + { + return (startX, startY, deltaX, deltaY); + } + } + } +} + +internal static class IOExtensions +{ + internal static (int X, int Y) ReadCoordinates(this IReadWrite io) + { + var (x, y) = io.Read2Numbers(""); + return ((int)x, (int)y); + } + + internal static (int X, int Y) ReadValidCoordinates(this IReadWrite io) + { + while (true) + { + var (x, y) = io.Read2Numbers(""); + if (x == (int)x && x is >= 1 and <= 10 && y == (int)y && y is >= 1 and <= 10) + { + return ((int)x, (int)y); + } + io.WriteLine("ILLEGAL, ENTER AGAIN."); + } + } +} From 050765e89f084d2f0e6fb470ebe31a92772403b8 Mon Sep 17 00:00:00 2001 From: Christopher Date: Mon, 6 Mar 2023 09:37:45 +1300 Subject: [PATCH 098/141] minor bug fix for 0 path trying to exit the perimeter --- 84_Super_Star_Trek/rust/src/commands.rs | 7 +++++++ 84_Super_Star_Trek/rust/src/view.rs | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/84_Super_Star_Trek/rust/src/commands.rs b/84_Super_Star_Trek/rust/src/commands.rs index d2eefc61..c1c4bc8d 100644 --- a/84_Super_Star_Trek/rust/src/commands.rs +++ b/84_Super_Star_Trek/rust/src/commands.rs @@ -151,6 +151,13 @@ fn move_enterprise(course: f32, warp_speed: f32, galaxy: &mut Galaxy) { } } + if path.len() == 0 { + if hit_edge { + view::hit_edge(ship.quadrant, ship.sector); + } + return; + } + let energy_cost = path.len() as u16 + 10; if energy_cost > ship.total_energy { diff --git a/84_Super_Star_Trek/rust/src/view.rs b/84_Super_Star_Trek/rust/src/view.rs index c276dee2..caf6b74d 100644 --- a/84_Super_Star_Trek/rust/src/view.rs +++ b/84_Super_Star_Trek/rust/src/view.rs @@ -199,7 +199,7 @@ pub fn intro(model: &Galaxy) { "Your orders are as follows: Destroy the {} Klingon warships which have invaded the galaxy before they can attack federation headquarters - on stardate {}. This gives you {} days. {} in the galaxy for resupplying your ship.\n", + on stardate {:.1}. This gives you {} days. {} in the galaxy for resupplying your ship.\n", model.remaining_klingons(), model.final_stardate, model.final_stardate - model.stardate, star_base_message) } @@ -253,7 +253,7 @@ pub fn short_range_scan(model: &Galaxy) { } let data : [String; 8] = [ - format!("Stardate {}", model.stardate), + format!("Stardate {:.1}", model.stardate), format!("Condition {}", condition), format!("Quadrant {}", model.enterprise.quadrant), format!("Sector {}", model.enterprise.sector), @@ -300,7 +300,7 @@ pub fn print_command_help() { pub fn end_game_failure(galaxy: &Galaxy) { println!( -"Is is stardate {}. +"Is is stardate {:.1}. There were {} Klingon battle cruisers left at the end of your mission. ", galaxy.stardate, galaxy.remaining_klingons()); @@ -519,7 +519,7 @@ pub fn starbase_shields() { pub fn repair_estimate(repair_time: f32) { println!( "Technicians standing by to effect repairs to your ship; -Estimated time to repair: {repair_time} stardates.") +Estimated time to repair: {repair_time:.1} stardates.") } pub fn no_damage(sector: Pos) { @@ -613,7 +613,7 @@ pub fn status_report(galaxy: &Galaxy) { println!(" Status report: Klingons left: {klingon_count} -Mission must be completed in {time_remaining} stardates. +Mission must be completed in {time_remaining:.1} stardates. The Federation is maintaining {star_bases} starbase{plural_starbase} in the galaxy. ") } From a6d191261dc8c245f433b73e0a509ac72a2f94d6 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Mon, 6 Mar 2023 08:50:21 +1100 Subject: [PATCH 099/141] Introduce Coordinate and Offset --- 77_Salvo/csharp/Game.cs | 175 +++++++++++++++++++++------------------- 1 file changed, 94 insertions(+), 81 deletions(-) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index 3c382521..4d4551d1 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -21,14 +21,10 @@ internal class Game { _io.Write(Streams.Title); - -L1040: - var humanGrid = new float[11,11]; - var shotsX = new int[8]; - var shotsY = new int[8]; +L1040: var humanGrid = new float[11,11]; var hitTurnRecord = new int[13]; - var tempX = new int[13]; - var tempY = new int[13]; + var shots = new Coordinates[8]; + var temp = new Coordinates[13]; var hitShipValue = new float[13]; L1060: for (var i = 1; i <= 12; i++) { @@ -39,14 +35,13 @@ L1190: var computerGrid = new float[11,11]; L1240: for (var K = 3; K >= 0; K--) { L1250: var shipGenerationAttempts=0; -L1260: var (startX, startY, deltaY, deltaX) = GetRandomShipCoordinatesInRange(K); +L1260: var (start, delta) = GetRandomShipCoordinatesInRange(K); L1340: shipGenerationAttempts++; L1350: if (shipGenerationAttempts>25) { goto L1190; } // determine ship coordinates -L1360: for (var i = 0; i <= _shipSize[K] - 1; i++) +L1360: for (var i = _shipFirstIndex[K]; i <= _shipFirstIndex[K] + _shipSize[K] - 1; i++) { - tempX[i+_shipFirstIndex[K]]=startX+deltaX*i; -L1380: tempY[i+_shipFirstIndex[K]]=startY+deltaY*i; + temp[i] = start + delta * i; } L1400: var firstIndex=_shipFirstIndex[K]; // detect proximity to previous ships @@ -55,13 +50,13 @@ L1400: var firstIndex=_shipFirstIndex[K]; L1415: if (firstIndex<2) { continue; } // Only true for the Battleship L1420: for (var j = 1; j <= firstIndex-1; j++) { -L1430: if (Math.Sqrt((tempX[j]-tempX[i])*(tempX[j]-tempX[i]) + (tempY[j]-tempY[i])*(tempY[j]-tempY[i])) < 3.59) { goto L1260; } +L1430: if (temp[j].DistanceTo(temp[i]) < 3.59) { goto L1260; } } } // put ship on board L1460: for (var i = firstIndex; i <= firstIndex + _shipSize[K] - 1; i++) { - computerGrid[tempX[i], tempY[i]] = _shipValue[K]; + computerGrid[temp[i].X, temp[i].Y] = _shipValue[K]; } } L1500: _io.WriteLine("ENTER COORDINATES FOR..."); @@ -95,18 +90,18 @@ L1730: while (startResponse == "WHERE ARE YOUR SHIPS?") _io.WriteLine("BATTLESHIP"); for (var i = 1; i <= 5; i++) { - _io.WriteLine($" {tempX[i]} {tempY[i]} "); + _io.WriteLine(temp[i]); } _io.WriteLine("CRUISER"); - _io.WriteLine($" {tempX[6]} {tempY[6]} "); - _io.WriteLine($" {tempX[7]} {tempY[7]} "); - _io.WriteLine($" {tempX[8]} {tempY[8]} "); + _io.WriteLine(temp[6]); + _io.WriteLine(temp[7]); + _io.WriteLine(temp[8]); _io.WriteLine("DESTROYER"); - _io.WriteLine($" {tempX[9]} {tempY[9]} "); - _io.WriteLine($" {tempX[10]} {tempY[10]} "); + _io.WriteLine(temp[9]); + _io.WriteLine(temp[10]); _io.WriteLine("DESTROYER"); - _io.WriteLine($" {tempX[11]} {tempY[11]} "); - _io.WriteLine($" {tempX[12]} {tempY[12]} "); + _io.WriteLine(temp[11]); + _io.WriteLine(temp[12]); startResponse = _io.ReadString("DO YOU WANT TO START"); } @@ -133,7 +128,7 @@ L2070: maxShotCount+=(int)(shipValue+.5F); } L2090: for (var i = 1; i <= 7; i++) { -L2100: shotsX[i] = shotsY[i] = tempX[i] = tempY[i] = 0; + shots[i] = temp[i] = 0; } L2150: var untriedSquareCount=0; L2160: for (var x = 1; x <= 10; x++) @@ -160,22 +155,21 @@ L2390: if (computerGrid[x,y]>10) _io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {computerGrid[x,y]-10}"); continue; } - shotsX[i]=x; - shotsY[i]=y; + shots[i]= new(x, y); break; } } L2460: for (var W = 1; W <= maxShotCount; W++) { - _io.WriteLine(computerGrid[shotsX[W],shotsY[W]] switch + _io.WriteLine(computerGrid[shots[W].X,shots[W].Y] switch { 3 => "YOU HIT MY BATTLESHIP.", 2 => "YOU HIT MY CRUISER.", 1 => "YOU HIT MY DESTROYER.", .5F => "YOU HIT MY DESTROYER.", - _ => throw new InvalidOperationException($"Unexpected value {computerGrid[shotsX[W],shotsY[W]]}") + _ => throw new InvalidOperationException($"Unexpected value {computerGrid[shots[W].X,shots[W].Y]}") }); -L2510: computerGrid[shotsX[W],shotsY[W]] = 10+turnNumber; +L2510: computerGrid[shots[W].X,shots[W].Y] = 10+turnNumber; } L2620: if (startResponse == "YES") { goto L2670; } L2640: turnNumber++; @@ -221,47 +215,30 @@ L2970: if (hitShipValue[i]>0) { goto L3800; } } L3000: var shotCount=0; L3010: var shotAttempts=0; -L3020: var (shotX, shotY, _, _) = GetRandomShipCoordinates(); -L3030: //RESTORE - var index = 0; -L3040: var strategyNumber=0; +L3020: var (shot, _) = GetRandomShipCoordinates(); +L3030: var strategyNumber=0; //RESTORE L3050: shotAttempts++; L3060: if (shotAttempts>100) { goto L3010; } // ensure shot is in range -L3070: if (shotX>10) { goto L3110; } -L3080: if (shotX>0) { goto L3120; } -L3090: shotX = 1 + (int)_random.NextFloat(2.5F); -L3100: goto L3120; -L3110: shotX = 10 - (int)_random.NextFloat(2.5F); -L3120: if (shotY>10) { goto L3160; } -L3130: if (shotY>0) { goto L3270; } -L3140: shotY=1+(int)_random.NextFloat(2.5F); -L3150: goto L3270; -L3160: shotY=10-(int)_random.NextFloat(2.5F); +L3070: shot = shot.BringIntoRange(_random); L3170: goto L3270; // record shot -L3180: tempX[shotCount]=shotX; -L3190: tempY[shotCount]=shotY; +L3180: temp[shotCount]=shot; L3200: if (shotCount==maxShotCount) { goto L3380; } L3210: if (strategyNumber==6) { goto L3030; } L3240: //DATA 1,1,-1,1,1,-3,1,1,0,2,-1,1 - var data = new[] { 1,1,-1,1,1,-3,1,1,0,2,-1,1 }; + var data = new Offset[] { new(1,1),new(-1,1),new(1,-3),new(1,1),new(0,2),new(-1,1) }; L3220: //READ X1,Y1 - var (xOffset, yOffset) = (data[index++], data[index++]); -L3230: strategyNumber++; -L3250: shotX+=xOffset; -L3260: shotY+=yOffset; + var offset = data[strategyNumber++]; +L3250: shot+=offset; // is the shot in range? -L3270: if (shotX>10) { goto L3210; } -L3280: if (shotX<1) { goto L3210; } -L3290: if (shotY>10) { goto L3210; } -L3300: if (shotY<1) { goto L3210; } +L3270: if (!shot.IsInRange) { goto L3210; } // have we fired here before -L3310: if (humanGrid[shotX,shotY]>10) { goto L3210; } +L3310: if (humanGrid[shot.X,shot.Y]>10) { goto L3210; } // have we already selected this shot? L3320: for (var i = 1; i <= shotCount; i++) { -L3330: if (tempX[i] == shotX && tempY[i] == shotY) { goto L3210; } +L3330: if (temp[i] == shot) { goto L3210; } } L3360: shotCount++; L3370: goto L3180; @@ -269,29 +246,29 @@ L3370: goto L3180; L3380: if (seeShotsResponse != "YES") { goto L3420; } L3390: for (var i = 1; i <= maxShotCount; i++) { -L3400: _io.WriteLine($" {tempX[i]} {tempY[i]}"); +L3400: _io.WriteLine(temp[i]); } L3420: for (var i = 1; i <= maxShotCount; i++) { -L3430: if (humanGrid[tempX[i],tempY[i]] == 3) +L3430: if (humanGrid[temp[i].X,temp[i].Y] == 3) { _io.WriteLine("I HIT YOUR BATTLESHIP"); } - else if (humanGrid[tempX[i],tempY[i]] == 2) + else if (humanGrid[temp[i].X,temp[i].Y] == 2) { _io.WriteLine("I HIT YOUR CRUISER"); } - else if (humanGrid[tempX[i],tempY[i]] == 1) + else if (humanGrid[temp[i].X,temp[i].Y] == 1) { _io.WriteLine("I HIT YOUR DESTROYER"); } - else if (humanGrid[tempX[i],tempY[i]] == .5F) + else if (humanGrid[temp[i].X,temp[i].Y] == .5F) { _io.WriteLine("I HIT YOUR DESTROYER"); } else { - humanGrid[tempX[i],tempY[i]]=10+turnNumber; + humanGrid[temp[i].X,temp[i].Y]=10+turnNumber; continue; } L3570: for (var j = 1; j <= 12; j++) @@ -299,7 +276,7 @@ L3570: for (var j = 1; j <= 12; j++) // record hit L3580: if (hitTurnRecord[j] != -1) { continue; } L3590: hitTurnRecord[j]=10+turnNumber; -L3600: hitShipValue[j]=humanGrid[tempX[i],tempY[i]]; +L3600: hitShipValue[j]=humanGrid[temp[i].X,temp[i].Y]; // look for past hits on same ship L3610: var shipHits=0; L3620: for (var k = 1; k <= 12; k++) @@ -327,11 +304,11 @@ L3760: _io.WriteLine($"{nameof(hitTurnRecord)}( {j} ) = {hitTurnRecord[ L3770: _io.WriteLine($"{nameof(hitShipValue)}( {j} ) = {hitShipValue[j]}"); } return; -L3470: humanGrid[tempX[i],tempY[i]]=10+turnNumber; +L3470: humanGrid[temp[i].X,temp[i].Y]=10+turnNumber; } L3490: goto L1950; L3800: //REM************************USINGEARRAY - var temp = new int[11,11]; + var tempGrid = new int[11,11]; L3860: for (var i = 1; i <= 12; i++) { L3870: if (hitTurnRecord[i]<10) { continue; } @@ -348,22 +325,21 @@ L3940: for (var dY = Math.Sign(1-y); dY <= Math.Sign(10-y); L3950: if (dX == 0 && dY ==0) { continue; } L3960: if (humanGrid[x+dX,y+dY] == hitTurnRecord[i]) { -L3970: temp[x,y]=temp[x,y]+hitTurnRecord[i]-y*(int)(hitShipValue[i]+.5F); +L3970: tempGrid[x,y] += hitTurnRecord[i]-y*(int)(hitShipValue[i]+.5F); } } } } else { -L3910: temp[x,y]=-10000000; +L3910: tempGrid[x,y]=-10000000; } } } } L4030: for (var i = 1; i <= maxShotCount; i++) { -L4040: tempX[i]=i; -L4050: tempY[i]=i; +L4040: temp[i]=i; } L4070: for (var x = 1; x <= 10; x++) { @@ -372,19 +348,18 @@ L4080: for (var y = 1; y <= 10; y++) L4090: var Q9=1; L4100: for (var i = 1; i <= maxShotCount; i++) { -L4110: if (temp[tempX[i],tempY[i]]>=temp[tempX[Q9],tempY[Q9]]) { continue; } +L4110: if (tempGrid[temp[i].X,temp[i].Y]>=tempGrid[temp[Q9].X,temp[Q9].Y]) { continue; } L4120: Q9=i; } L4131: if (x>maxShotCount) { goto L4140; } L4132: if (x==y) { goto L4210; } -L4140: if (temp[x,y]=1 && endX<=10 && endY>=1 && endY<=10) + var end = start + delta * shipSizeLessOne; + if (delta != 0 && end.IsInRange) { - return (startX, startY, deltaX, deltaY); + return (start, delta); } } } @@ -438,3 +412,42 @@ internal static class IOExtensions } } } + +internal record struct Coordinates(int X, int Y) +{ + internal Coordinates(float x, float y) + : this((int)x, (int)y) + { + } + + public bool IsInRange => X is >= 1 and <= 10 && Y is >= 1 and <= 10; + + internal double DistanceTo(Coordinates other) + => Math.Sqrt((X - other.X) * (X - other.Y) + (Y - other.Y) * (Y - other.Y)); + + internal Coordinates BringIntoRange(IRandom random) + => IsInRange ? this : new(BringIntoRange(X, random), BringIntoRange(Y, random)); + + private int BringIntoRange(int value, IRandom random) + => value switch + { + < 1 => 1 + (int)random.NextFloat(2.5F), + > 10 => 10 - (int)random.NextFloat(2.5F), + _ => value + }; + + public static Coordinates operator +(Coordinates coordinates, Offset offset) + => new(coordinates.X + offset.X, coordinates.Y + offset.Y); + + public static implicit operator Coordinates(int value) => new(value, value); + + public override string ToString() => $" {X} {Y} "; +} + +internal record struct Offset(int X, int Y) +{ + public static Offset operator *(Offset offset, int scale) + => new(offset.X * scale, offset.Y * scale); + + public static implicit operator Offset(int value) => new(value, value); +} From 0089593453f5a025318d049cdce27b8a3d06c663 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Tue, 7 Mar 2023 08:34:55 +1100 Subject: [PATCH 100/141] Add coordinate loops --- 77_Salvo/csharp/Game.cs | 107 ++++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 37 deletions(-) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index 4d4551d1..1bdeed5f 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -311,29 +311,22 @@ L3800: //REM************************USINGEARRAY var tempGrid = new int[11,11]; L3860: for (var i = 1; i <= 12; i++) { -L3870: if (hitTurnRecord[i]<10) { continue; } -L3880: for (var x = 1; x <= 10; x++) + if (hitTurnRecord[i]<10) { continue; } + foreach (var position in Coordinates.All) { -L3890: for (var y = 1; y <= 10; y++) - { -L3900: if (humanGrid[x,y]>=10) - { -L3930: for (var dX = Math.Sign(1-x); dX <= Math.Sign(10-x); dX++) + if (humanGrid[position.X,position.Y]>=10) + { + foreach (var neighbour in position.Neighbours) + { + if (humanGrid[neighbour.X,neighbour.Y] == hitTurnRecord[i]) { -L3940: for (var dY = Math.Sign(1-y); dY <= Math.Sign(10-y); dY++) - { -L3950: if (dX == 0 && dY ==0) { continue; } -L3960: if (humanGrid[x+dX,y+dY] == hitTurnRecord[i]) - { -L3970: tempGrid[x,y] += hitTurnRecord[i]-y*(int)(hitShipValue[i]+.5F); - } - } + tempGrid[position.X,position.Y] += hitTurnRecord[i]-position.Y*(int)(hitShipValue[i]+.5F); } } - else - { -L3910: tempGrid[x,y]=-10000000; - } + } + else + { + tempGrid[position.X,position.Y]=-10000000; } } } @@ -341,27 +334,24 @@ L4030: for (var i = 1; i <= maxShotCount; i++) { L4040: temp[i]=i; } -L4070: for (var x = 1; x <= 10; x++) + foreach (var position in Coordinates.All) { -L4080: for (var y = 1; y <= 10; y++) +L4090: var Q9=1; +L4100: for (var i = 1; i <= maxShotCount; i++) { -L4090: var Q9=1; -L4100: for (var i = 1; i <= maxShotCount; i++) - { -L4110: if (tempGrid[temp[i].X,temp[i].Y]>=tempGrid[temp[Q9].X,temp[Q9].Y]) { continue; } -L4120: Q9=i; - } -L4131: if (x>maxShotCount) { goto L4140; } -L4132: if (x==y) { goto L4210; } -L4140: if (tempGrid[x,y]=tempGrid[temp[Q9].X,temp[Q9].Y]) { continue; } +L4120: Q9=i; } +L4131: if (x>maxShotCount) { goto L4140; } +L4132: if (x==y) { goto L4210; } +L4140: if (tempGrid[x,y] X is >= 1 and <= 10 && Y is >= 1 and <= 10; + public static IEnumerable All + { + get + { + for (int x = 1; x <= 10; x++) + { + for (int y = 1; y <= 10; y++) + { + yield return new(x, y); + } + } + } + } + + public IEnumerable Neighbours + { + get + { + foreach (var offset in Offset.Units) + { + var neighbour = this + offset; + if (neighbour.IsInRange) { yield return neighbour; } + } + } + } + internal double DistanceTo(Coordinates other) => Math.Sqrt((X - other.X) * (X - other.Y) + (Y - other.Y) * (Y - other.Y)); @@ -446,8 +462,25 @@ internal record struct Coordinates(int X, int Y) internal record struct Offset(int X, int Y) { + public static readonly Offset Zero = 0; + public static Offset operator *(Offset offset, int scale) => new(offset.X * scale, offset.Y * scale); public static implicit operator Offset(int value) => new(value, value); + + public static IEnumerable Units + { + get + { + for (int x = -1; x <= 1; x++) + { + for (int y = -1; y <= 1; y++) + { + var offset = new Offset(x, y); + if (offset != Zero) { yield return offset; } + } + } + } + } } From ee9bfec90ee335549257618a933ea29a84271fbf Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Wed, 8 Mar 2023 08:13:00 +1100 Subject: [PATCH 101/141] Introduce Grid --- 77_Salvo/csharp/Game.cs | 170 ++++++++++++++++++++++------------------ 1 file changed, 92 insertions(+), 78 deletions(-) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index 1bdeed5f..03a11197 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -21,7 +21,7 @@ internal class Game { _io.Write(Streams.Title); -L1040: var humanGrid = new float[11,11]; +L1040: var humanGrid = new Grid(); var hitTurnRecord = new int[13]; var shots = new Coordinates[8]; var temp = new Coordinates[13]; @@ -31,7 +31,7 @@ L1060: for (var i = 1; i <= 12; i++) L1070: hitTurnRecord[i] = -1; L1080: hitShipValue[i] = -1; } -L1190: var computerGrid = new float[11,11]; +L1190: var computerGrid = new Grid(); L1240: for (var K = 3; K >= 0; K--) { L1250: var shipGenerationAttempts=0; @@ -56,36 +56,32 @@ L1430: if (temp[j].DistanceTo(temp[i]) < 3.59) { goto L1260; } // put ship on board L1460: for (var i = firstIndex; i <= firstIndex + _shipSize[K] - 1; i++) { - computerGrid[temp[i].X, temp[i].Y] = _shipValue[K]; + computerGrid[temp[i]] = _shipValue[K]; } } -L1500: _io.WriteLine("ENTER COORDINATES FOR..."); -L1510: _io.WriteLine("BATTLESHIP"); -L1520: for (var i = 1; i <= 5; i++) + _io.WriteLine("ENTER COORDINATES FOR..."); + _io.WriteLine("BATTLESHIP"); + for (var i = 1; i <= 5; i++) { - var (x, y) = _io.ReadCoordinates(); -L1540: humanGrid[x, y] = 3; + humanGrid[_io.ReadCoordinates()] = 3; } -L1560: _io.WriteLine("CRUISER"); -L1570: for (var i = 1; i <= 3; i++) + _io.WriteLine("CRUISER"); + for (var i = 1; i <= 3; i++) { - var (x, y) = _io.ReadCoordinates(); -L1590: humanGrid[x, y] = 2; + humanGrid[_io.ReadCoordinates()] = 2; } -L1610: _io.WriteLine("DESTROYER"); -L1620: for (var i = 1; i <= 2; i++) + _io.WriteLine("DESTROYER"); + for (var i = 1; i <= 2; i++) { - var (x, y) = _io.ReadCoordinates(); -L1640: humanGrid[x, y] = 1; + humanGrid[_io.ReadCoordinates()] = 1; } -L1660: _io.WriteLine("DESTROYER"); -L1670: for (var i = 1; i <= 2; i++) + _io.WriteLine("DESTROYER"); + for (var i = 1; i <= 2; i++) { - var (x, y) = _io.ReadCoordinates(); -L1690: humanGrid[x, y] = 0.5F; + humanGrid[_io.ReadCoordinates()] = 0.5F; } -L1710: var startResponse = _io.ReadString("DO YOU WANT TO START"); -L1730: while (startResponse == "WHERE ARE YOUR SHIPS?") + var startResponse = _io.ReadString("DO YOU WANT TO START"); + while (startResponse == "WHERE ARE YOUR SHIPS?") { _io.WriteLine("BATTLESHIP"); for (var i = 1; i <= 5; i++) @@ -116,27 +112,23 @@ L1980: _io.WriteLine($"TURN {turnNumber}"); L1990: var maxShotCount=0; L2000: for (var shipValue = .5F; shipValue <= 3; shipValue += .5F) { -L2010: for (var x = 1; x <= 10; x++) + foreach (var position in Coordinates.All) { -L2020: for (var y = 1; y <= 10; y++) - { -L2030: if (humanGrid[x,y] == shipValue) { goto L2070; } + if (humanGrid[position] == shipValue) + { + maxShotCount+=(int)(shipValue+.5F); + break; } } -L2060: continue; -L2070: maxShotCount+=(int)(shipValue+.5F); } L2090: for (var i = 1; i <= 7; i++) { shots[i] = temp[i] = 0; } L2150: var untriedSquareCount=0; -L2160: for (var x = 1; x <= 10; x++) + foreach (var position in Coordinates.All) { -L2170: for (var y = 1; y <= 10; y++) - { -L2180: if (computerGrid[x,y] <= 10) { untriedSquareCount++; } - } + if (computerGrid[position.X] <= 10) { untriedSquareCount++; } } L2220: _io.WriteLine($"YOU HAVE {maxShotCount} SHOTS."); if (maxShotCount == 0) { goto L2270; } @@ -149,27 +141,27 @@ L2290: for (var i = 1; i <= maxShotCount; i++) { while (true) { - var (x, y) = _io.ReadValidCoordinates(); -L2390: if (computerGrid[x,y]>10) + var position = _io.ReadValidCoordinates(); +L2390: if (computerGrid[position]>10) { - _io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {computerGrid[x,y]-10}"); + _io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {computerGrid[position]-10}"); continue; } - shots[i]= new(x, y); + shots[i]= position; break; } } L2460: for (var W = 1; W <= maxShotCount; W++) { - _io.WriteLine(computerGrid[shots[W].X,shots[W].Y] switch + _io.WriteLine(computerGrid[shots[W]] switch { 3 => "YOU HIT MY BATTLESHIP.", 2 => "YOU HIT MY CRUISER.", 1 => "YOU HIT MY DESTROYER.", .5F => "YOU HIT MY DESTROYER.", - _ => throw new InvalidOperationException($"Unexpected value {computerGrid[shots[W].X,shots[W].Y]}") + _ => throw new InvalidOperationException($"Unexpected value {computerGrid[shots[W]]}") }); -L2510: computerGrid[shots[W].X,shots[W].Y] = 10+turnNumber; +L2510: computerGrid[shots[W]] = 10+turnNumber; } L2620: if (startResponse == "YES") { goto L2670; } L2640: turnNumber++; @@ -178,24 +170,19 @@ L2660: _io.WriteLine($"TURN {turnNumber}"); L2670: maxShotCount = 0; L2680: for (var shipValue = .5F; shipValue <= 3; shipValue += .5F) { -L2690: for (var x = 1; x <= 10; x++) + foreach (var position in Coordinates.All) { -L2700: for (var y = 1; y <= 10; y++) - { -L2710: if (computerGrid[x,y] == shipValue) { goto L2750; } + if (computerGrid[position] == shipValue) + { + maxShotCount += (int)(shipValue+.5F); + break; } } -L2740: continue; -L2750: maxShotCount += (int)(shipValue+.5F); } L2770: untriedSquareCount=0; -L2780: for (var x = 1; x <= 10; x++) + foreach (var position in Coordinates.All) { -L2790: for (var y = 1; y <= 10; y++) - { -L2800: if (computerGrid[x,y]>10) { continue; } -L2810: untriedSquareCount++; - } + if (computerGrid[position]<=10) { untriedSquareCount++; } } L2840: _io.WriteLine($"I HAVE {maxShotCount} SHOTS."); L2850: if (untriedSquareCount>maxShotCount) { goto L2880; } @@ -234,7 +221,7 @@ L3250: shot+=offset; // is the shot in range? L3270: if (!shot.IsInRange) { goto L3210; } // have we fired here before -L3310: if (humanGrid[shot.X,shot.Y]>10) { goto L3210; } +L3310: if (humanGrid[shot]>10) { goto L3210; } // have we already selected this shot? L3320: for (var i = 1; i <= shotCount; i++) { @@ -250,25 +237,25 @@ L3400: _io.WriteLine(temp[i]); } L3420: for (var i = 1; i <= maxShotCount; i++) { -L3430: if (humanGrid[temp[i].X,temp[i].Y] == 3) +L3430: if (humanGrid[temp[i]] == 3) { _io.WriteLine("I HIT YOUR BATTLESHIP"); } - else if (humanGrid[temp[i].X,temp[i].Y] == 2) + else if (humanGrid[temp[i]] == 2) { _io.WriteLine("I HIT YOUR CRUISER"); } - else if (humanGrid[temp[i].X,temp[i].Y] == 1) + else if (humanGrid[temp[i]] == 1) { _io.WriteLine("I HIT YOUR DESTROYER"); } - else if (humanGrid[temp[i].X,temp[i].Y] == .5F) + else if (humanGrid[temp[i]] == .5F) { _io.WriteLine("I HIT YOUR DESTROYER"); } else { - humanGrid[temp[i].X,temp[i].Y]=10+turnNumber; + humanGrid[temp[i]]=10+turnNumber; continue; } L3570: for (var j = 1; j <= 12; j++) @@ -276,7 +263,7 @@ L3570: for (var j = 1; j <= 12; j++) // record hit L3580: if (hitTurnRecord[j] != -1) { continue; } L3590: hitTurnRecord[j]=10+turnNumber; -L3600: hitShipValue[j]=humanGrid[temp[i].X,temp[i].Y]; +L3600: hitShipValue[j]=humanGrid[temp[i]]; // look for past hits on same ship L3610: var shipHits=0; L3620: for (var k = 1; k <= 12; k++) @@ -304,29 +291,29 @@ L3760: _io.WriteLine($"{nameof(hitTurnRecord)}( {j} ) = {hitTurnRecord[ L3770: _io.WriteLine($"{nameof(hitShipValue)}( {j} ) = {hitShipValue[j]}"); } return; -L3470: humanGrid[temp[i].X,temp[i].Y]=10+turnNumber; +L3470: humanGrid[temp[i]]=10+turnNumber; } L3490: goto L1950; L3800: //REM************************USINGEARRAY - var tempGrid = new int[11,11]; + var tempGrid = new Grid(); L3860: for (var i = 1; i <= 12; i++) { if (hitTurnRecord[i]<10) { continue; } foreach (var position in Coordinates.All) { - if (humanGrid[position.X,position.Y]>=10) + if (humanGrid[position]>=10) { foreach (var neighbour in position.Neighbours) { - if (humanGrid[neighbour.X,neighbour.Y] == hitTurnRecord[i]) + if (humanGrid[neighbour] == hitTurnRecord[i]) { - tempGrid[position.X,position.Y] += hitTurnRecord[i]-position.Y*(int)(hitShipValue[i]+.5F); + tempGrid[position] += hitTurnRecord[i]-position.Y*(int)(hitShipValue[i]+.5F); } } } else { - tempGrid[position.X,position.Y]=-10000000; + tempGrid[position]=-10000000; } } } @@ -339,22 +326,21 @@ L4040: temp[i]=i; L4090: var Q9=1; L4100: for (var i = 1; i <= maxShotCount; i++) { -L4110: if (tempGrid[temp[i].X,temp[i].Y]>=tempGrid[temp[Q9].X,temp[Q9].Y]) { continue; } +L4110: if (tempGrid[temp[i]]>=tempGrid[temp[Q9]]) { continue; } L4120: Q9=i; } -L4131: if (x>maxShotCount) { goto L4140; } -L4132: if (x==y) { goto L4210; } -L4140: if (tempGrid[x,y]maxShotCount) { goto L4140; } +L4132: if (position.IsOnDiagonal) { goto L4210; } +L4140: if (tempGrid[position] _positions[position.X - 1, position.Y - 1]; + set => _positions[position.X - 1, position.Y - 1] = value; + } +} + internal static class IOExtensions { - internal static (int X, int Y) ReadCoordinates(this IReadWrite io) + internal static Coordinates ReadCoordinates(this IReadWrite io) { var (x, y) = io.Read2Numbers(""); - return ((int)x, (int)y); + return new(x, y); } - internal static (int X, int Y) ReadValidCoordinates(this IReadWrite io) + internal static Coordinates ReadValidCoordinates(this IReadWrite io) { while (true) { var (x, y) = io.Read2Numbers(""); - if (x == (int)x && x is >= 1 and <= 10 && y == (int)y && y is >= 1 and <= 10) + if (Coordinates.TryCreateValid(x, y, out var position)) { - return ((int)x, (int)y); + return position; } io.WriteLine("ILLEGAL, ENTER AGAIN."); } @@ -411,6 +408,23 @@ internal record struct Coordinates(int X, int Y) } public bool IsInRange => X is >= 1 and <= 10 && Y is >= 1 and <= 10; + public bool IsOnDiagonal => X == Y; + + public static bool TryCreateValid(float x, float y, out Coordinates coordinates) + { + coordinates = default; + if (x != (int)x || y != (int)y) { return false; } + + var result = new Coordinates(x, y); + + if (result.IsInRange) + { + coordinates = result; + return true; + } + + return false; + } public static IEnumerable All { From a0683190347402c98ed3f910f1cf7223fafd1dd7 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Wed, 8 Mar 2023 08:17:56 +1100 Subject: [PATCH 102/141] Rename Coordinates to Position --- 77_Salvo/csharp/Game.cs | 79 ++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index 03a11197..bd33c3d2 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -23,8 +23,8 @@ internal class Game L1040: var humanGrid = new Grid(); var hitTurnRecord = new int[13]; - var shots = new Coordinates[8]; - var temp = new Coordinates[13]; + var shots = new Position[8]; + var temp = new Position[13]; var hitShipValue = new float[13]; L1060: for (var i = 1; i <= 12; i++) { @@ -35,10 +35,10 @@ L1190: var computerGrid = new Grid(); L1240: for (var K = 3; K >= 0; K--) { L1250: var shipGenerationAttempts=0; -L1260: var (start, delta) = GetRandomShipCoordinatesInRange(K); +L1260: var (start, delta) = GetRandomShipPositionInRange(K); L1340: shipGenerationAttempts++; L1350: if (shipGenerationAttempts>25) { goto L1190; } - // determine ship coordinates + // determine ship position L1360: for (var i = _shipFirstIndex[K]; i <= _shipFirstIndex[K] + _shipSize[K] - 1; i++) { temp[i] = start + delta * i; @@ -59,26 +59,26 @@ L1460: for (var i = firstIndex; i <= firstIndex + _shipSize[K] - 1; i++) computerGrid[temp[i]] = _shipValue[K]; } } - _io.WriteLine("ENTER COORDINATES FOR..."); + _io.WriteLine("ENTER POSITION FOR..."); _io.WriteLine("BATTLESHIP"); for (var i = 1; i <= 5; i++) { - humanGrid[_io.ReadCoordinates()] = 3; + humanGrid[_io.ReadPosition()] = 3; } _io.WriteLine("CRUISER"); for (var i = 1; i <= 3; i++) { - humanGrid[_io.ReadCoordinates()] = 2; + humanGrid[_io.ReadPosition()] = 2; } _io.WriteLine("DESTROYER"); for (var i = 1; i <= 2; i++) { - humanGrid[_io.ReadCoordinates()] = 1; + humanGrid[_io.ReadPosition()] = 1; } _io.WriteLine("DESTROYER"); for (var i = 1; i <= 2; i++) { - humanGrid[_io.ReadCoordinates()] = 0.5F; + humanGrid[_io.ReadPosition()] = 0.5F; } var startResponse = _io.ReadString("DO YOU WANT TO START"); while (startResponse == "WHERE ARE YOUR SHIPS?") @@ -112,7 +112,7 @@ L1980: _io.WriteLine($"TURN {turnNumber}"); L1990: var maxShotCount=0; L2000: for (var shipValue = .5F; shipValue <= 3; shipValue += .5F) { - foreach (var position in Coordinates.All) + foreach (var position in Position.All) { if (humanGrid[position] == shipValue) { @@ -126,7 +126,7 @@ L2090: for (var i = 1; i <= 7; i++) shots[i] = temp[i] = 0; } L2150: var untriedSquareCount=0; - foreach (var position in Coordinates.All) + foreach (var position in Position.All) { if (computerGrid[position.X] <= 10) { untriedSquareCount++; } } @@ -141,7 +141,7 @@ L2290: for (var i = 1; i <= maxShotCount; i++) { while (true) { - var position = _io.ReadValidCoordinates(); + var position = _io.ReadValidPosition(); L2390: if (computerGrid[position]>10) { _io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {computerGrid[position]-10}"); @@ -170,7 +170,7 @@ L2660: _io.WriteLine($"TURN {turnNumber}"); L2670: maxShotCount = 0; L2680: for (var shipValue = .5F; shipValue <= 3; shipValue += .5F) { - foreach (var position in Coordinates.All) + foreach (var position in Position.All) { if (computerGrid[position] == shipValue) { @@ -180,7 +180,7 @@ L2680: for (var shipValue = .5F; shipValue <= 3; shipValue += .5F) } } L2770: untriedSquareCount=0; - foreach (var position in Coordinates.All) + foreach (var position in Position.All) { if (computerGrid[position]<=10) { untriedSquareCount++; } } @@ -202,7 +202,7 @@ L2970: if (hitShipValue[i]>0) { goto L3800; } } L3000: var shotCount=0; L3010: var shotAttempts=0; -L3020: var (shot, _) = GetRandomShipCoordinates(); +L3020: var (shot, _) = GetRandomShipPosition(); L3030: var strategyNumber=0; //RESTORE L3050: shotAttempts++; L3060: if (shotAttempts>100) { goto L3010; } @@ -299,7 +299,7 @@ L3800: //REM************************USINGEARRAY L3860: for (var i = 1; i <= 12; i++) { if (hitTurnRecord[i]<10) { continue; } - foreach (var position in Coordinates.All) + foreach (var position in Position.All) { if (humanGrid[position]>=10) { @@ -321,7 +321,7 @@ L4030: for (var i = 1; i <= maxShotCount; i++) { L4040: temp[i]=i; } - foreach (var position in Coordinates.All) + foreach (var position in Position.All) { L4090: var Q9=1; L4100: for (var i = 1; i <= maxShotCount; i++) @@ -343,7 +343,7 @@ L4210: ;// NoOp - NEXT S L4230: goto L3380; } - private (Coordinates, Offset) GetRandomShipCoordinates() + private (Position, Offset) GetRandomShipPosition() { var startX = _random.Next(1, 11); var startY = _random.Next(1, 11); @@ -352,11 +352,11 @@ L4230: goto L3380; return (new(startX, startY), new(deltaX, deltaY)); } - private (Coordinates, Offset) GetRandomShipCoordinatesInRange(int shipNumber) + private (Position, Offset) GetRandomShipPositionInRange(int shipNumber) { while (true) { - var (start, delta) = GetRandomShipCoordinates(); + var (start, delta) = GetRandomShipPosition(); var shipSizeLessOne = _shipSize[shipNumber] - 1; var end = start + delta * shipSizeLessOne; if (delta != 0 && end.IsInRange) @@ -371,7 +371,7 @@ internal class Grid { private readonly float[,] _positions = new float[10, 10]; - public float this[Coordinates position] + public float this[Position position] { get => _positions[position.X - 1, position.Y - 1]; set => _positions[position.X - 1, position.Y - 1] = value; @@ -380,18 +380,18 @@ internal class Grid internal static class IOExtensions { - internal static Coordinates ReadCoordinates(this IReadWrite io) + internal static Position ReadPosition(this IReadWrite io) { var (x, y) = io.Read2Numbers(""); return new(x, y); } - internal static Coordinates ReadValidCoordinates(this IReadWrite io) + internal static Position ReadValidPosition(this IReadWrite io) { while (true) { var (x, y) = io.Read2Numbers(""); - if (Coordinates.TryCreateValid(x, y, out var position)) + if (Position.TryCreateValid(x, y, out var position)) { return position; } @@ -400,9 +400,9 @@ internal static class IOExtensions } } -internal record struct Coordinates(int X, int Y) +internal record struct Position(int X, int Y) { - internal Coordinates(float x, float y) + internal Position(float x, float y) : this((int)x, (int)y) { } @@ -410,23 +410,23 @@ internal record struct Coordinates(int X, int Y) public bool IsInRange => X is >= 1 and <= 10 && Y is >= 1 and <= 10; public bool IsOnDiagonal => X == Y; - public static bool TryCreateValid(float x, float y, out Coordinates coordinates) + public static bool TryCreateValid(float x, float y, out Position position) { - coordinates = default; + position = default; if (x != (int)x || y != (int)y) { return false; } - var result = new Coordinates(x, y); + var result = new Position(x, y); if (result.IsInRange) { - coordinates = result; + position = result; return true; } return false; } - public static IEnumerable All + public static IEnumerable All { get { @@ -440,7 +440,7 @@ internal record struct Coordinates(int X, int Y) } } - public IEnumerable Neighbours + public IEnumerable Neighbours { get { @@ -452,13 +452,13 @@ internal record struct Coordinates(int X, int Y) } } - internal double DistanceTo(Coordinates other) + internal double DistanceTo(Position other) => Math.Sqrt((X - other.X) * (X - other.Y) + (Y - other.Y) * (Y - other.Y)); - internal Coordinates BringIntoRange(IRandom random) + internal Position BringIntoRange(IRandom random) => IsInRange ? this : new(BringIntoRange(X, random), BringIntoRange(Y, random)); - private int BringIntoRange(int value, IRandom random) + private static int BringIntoRange(int value, IRandom random) => value switch { < 1 => 1 + (int)random.NextFloat(2.5F), @@ -466,10 +466,10 @@ internal record struct Coordinates(int X, int Y) _ => value }; - public static Coordinates operator +(Coordinates coordinates, Offset offset) - => new(coordinates.X + offset.X, coordinates.Y + offset.Y); + public static Position operator +(Position position, Offset offset) + => new(position.X + offset.X, position.Y + offset.Y); - public static implicit operator Coordinates(int value) => new(value, value); + public static implicit operator Position(int value) => new(value, value); public override string ToString() => $" {X} {Y} "; } @@ -478,8 +478,7 @@ internal record struct Offset(int X, int Y) { public static readonly Offset Zero = 0; - public static Offset operator *(Offset offset, int scale) - => new(offset.X * scale, offset.Y * scale); + public static Offset operator *(Offset offset, int scale) => new(offset.X * scale, offset.Y * scale); public static implicit operator Offset(int value) => new(value, value); From a936de69d1da40bc3c05b149db02f3a2342a3ed3 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Thu, 9 Mar 2023 08:17:39 +1100 Subject: [PATCH 103/141] Introduce Coordinate --- 77_Salvo/csharp/Game.cs | 104 ++++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 47 deletions(-) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index bd33c3d2..233487d3 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -128,7 +128,7 @@ L2090: for (var i = 1; i <= 7; i++) L2150: var untriedSquareCount=0; foreach (var position in Position.All) { - if (computerGrid[position.X] <= 10) { untriedSquareCount++; } + if (computerGrid[position] <= 10) { untriedSquareCount++; } } L2220: _io.WriteLine($"YOU HAVE {maxShotCount} SHOTS."); if (maxShotCount == 0) { goto L2270; } @@ -373,25 +373,20 @@ internal class Grid public float this[Position position] { - get => _positions[position.X - 1, position.Y - 1]; - set => _positions[position.X - 1, position.Y - 1] = value; + get => _positions[position.X, position.Y]; + set => _positions[position.X, position.Y] = value; } } internal static class IOExtensions { - internal static Position ReadPosition(this IReadWrite io) - { - var (x, y) = io.Read2Numbers(""); - return new(x, y); - } + internal static Position ReadPosition(this IReadWrite io) => Position.Create(io.Read2Numbers("")); internal static Position ReadValidPosition(this IReadWrite io) { while (true) { - var (x, y) = io.Read2Numbers(""); - if (Position.TryCreateValid(x, y, out var position)) + if (Position.TryCreateValid(io.Read2Numbers(""), out var position)) { return position; } @@ -400,45 +395,27 @@ internal static class IOExtensions } } -internal record struct Position(int X, int Y) +internal record struct Position(Coordinate X, Coordinate Y) { - internal Position(float x, float y) - : this((int)x, (int)y) - { - } - - public bool IsInRange => X is >= 1 and <= 10 && Y is >= 1 and <= 10; + public bool IsInRange => X.IsInRange && Y.IsInRange; public bool IsOnDiagonal => X == Y; - public static bool TryCreateValid(float x, float y, out Position position) + public static Position Create((float X, float Y) coordinates) => new(coordinates.X, coordinates.Y); + + public static bool TryCreateValid((float X, float Y) coordinates, out Position position) { - position = default; - if (x != (int)x || y != (int)y) { return false; } - - var result = new Position(x, y); - - if (result.IsInRange) + if (Coordinate.TryCreateValid(coordinates.X, out var x) && Coordinate.TryCreateValid(coordinates.Y, out var y)) { - position = result; + position = new(x, y); return true; } + position = default; return false; } public static IEnumerable All - { - get - { - for (int x = 1; x <= 10; x++) - { - for (int y = 1; y <= 10; y++) - { - yield return new(x, y); - } - } - } - } + => Coordinate.Range.SelectMany(x => Coordinate.Range.Select(y => new Position(x, y))); public IEnumerable Neighbours { @@ -456,22 +433,55 @@ internal record struct Position(int X, int Y) => Math.Sqrt((X - other.X) * (X - other.Y) + (Y - other.Y) * (Y - other.Y)); internal Position BringIntoRange(IRandom random) - => IsInRange ? this : new(BringIntoRange(X, random), BringIntoRange(Y, random)); - - private static int BringIntoRange(int value, IRandom random) - => value switch - { - < 1 => 1 + (int)random.NextFloat(2.5F), - > 10 => 10 - (int)random.NextFloat(2.5F), - _ => value - }; + => IsInRange ? this : new(X.BringIntoRange(random), Y.BringIntoRange(random)); public static Position operator +(Position position, Offset offset) => new(position.X + offset.X, position.Y + offset.Y); public static implicit operator Position(int value) => new(value, value); - public override string ToString() => $" {X} {Y} "; + public override string ToString() => $"{X}{Y}"; +} + +internal record struct Coordinate(int Value) +{ + public static IEnumerable Range => Enumerable.Range(0, 10).Select(v => new Coordinate(v)); + + public bool IsInRange => Value is >= 0 and <= 9; + + public static Coordinate Create(float value) => new((int)value - 1); + + public static bool TryCreateValid(float value, out Coordinate coordinate) + { + coordinate = default; + if (value != (int)value) { return false; } + + var result = Create(value); + + if (result.IsInRange) + { + coordinate = result; + return true; + } + + return false; + } + + public Coordinate BringIntoRange(IRandom random) + => Value switch + { + < 0 => new(0 + (int)random.NextFloat(2.5F)), + > 9 => new(9 - (int)random.NextFloat(2.5F)), + _ => this + }; + + public static implicit operator Coordinate(float value) => new((int)value); + public static implicit operator int(Coordinate coordinate) => coordinate.Value; + + public static Coordinate operator +(Coordinate coordinate, int offset) => new(coordinate.Value + offset); + public static int operator -(Coordinate a, Coordinate b) => a.Value - b.Value; + + public override string ToString() => $" {Value + 1} "; } internal record struct Offset(int X, int Y) From 579e18d8c63e1d45c8a8749c7b0ab7a9b648892f Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Mon, 13 Mar 2023 08:33:48 +1100 Subject: [PATCH 104/141] Fix some bugs --- 77_Salvo/csharp/Game.cs | 48 +++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index 233487d3..bcc16477 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -35,13 +35,13 @@ L1190: var computerGrid = new Grid(); L1240: for (var K = 3; K >= 0; K--) { L1250: var shipGenerationAttempts=0; -L1260: var (start, delta) = GetRandomShipPositionInRange(K); +L1260: var (start, delta) = _random.GetRandomShipPositionInRange(_shipSize[K]); L1340: shipGenerationAttempts++; L1350: if (shipGenerationAttempts>25) { goto L1190; } // determine ship position -L1360: for (var i = _shipFirstIndex[K]; i <= _shipFirstIndex[K] + _shipSize[K] - 1; i++) +L1360: for (var i = 0; i < _shipSize[K]; i++) { - temp[i] = start + delta * i; + temp[_shipFirstIndex[K] + i] = start + delta * i; } L1400: var firstIndex=_shipFirstIndex[K]; // detect proximity to previous ships @@ -153,14 +153,15 @@ L2390: if (computerGrid[position]>10) } L2460: for (var W = 1; W <= maxShotCount; W++) { - _io.WriteLine(computerGrid[shots[W]] switch + var hit = computerGrid[shots[W]] switch { 3 => "YOU HIT MY BATTLESHIP.", 2 => "YOU HIT MY CRUISER.", 1 => "YOU HIT MY DESTROYER.", .5F => "YOU HIT MY DESTROYER.", - _ => throw new InvalidOperationException($"Unexpected value {computerGrid[shots[W]]}") - }); + _ => null + }; + if (hit is not null) { _io.WriteLine(); } L2510: computerGrid[shots[W]] = 10+turnNumber; } L2620: if (startResponse == "YES") { goto L2670; } @@ -202,7 +203,7 @@ L2970: if (hitShipValue[i]>0) { goto L3800; } } L3000: var shotCount=0; L3010: var shotAttempts=0; -L3020: var (shot, _) = GetRandomShipPosition(); +L3020: var (shot, _) = _random.NextShipPosition(); L3030: var strategyNumber=0; //RESTORE L3050: shotAttempts++; L3060: if (shotAttempts>100) { goto L3010; } @@ -342,22 +343,30 @@ L4210: ;// NoOp - NEXT S } L4230: goto L3380; } +} - private (Position, Offset) GetRandomShipPosition() +internal static class RandomExtensions +{ + internal static (Position, Offset) NextShipPosition(this IRandom random) { - var startX = _random.Next(1, 11); - var startY = _random.Next(1, 11); - var deltaY = _random.Next(-1, 2); - var deltaX = _random.Next(-1, 2); + var startX = random.NextCoordinate(); + var startY = random.NextCoordinate(); + var deltaY = random.NextOffset(); + var deltaX = random.NextOffset(); return (new(startX, startY), new(deltaX, deltaY)); } - private (Position, Offset) GetRandomShipPositionInRange(int shipNumber) + private static Coordinate NextCoordinate(this IRandom random) + => random.Next(Coordinate.MinValue, Coordinate.MaxValue + 1); + + private static int NextOffset(this IRandom random) => random.Next(-1, 2); + + internal static (Position, Offset) GetRandomShipPositionInRange(this IRandom random, int shipSize) { while (true) { - var (start, delta) = GetRandomShipPosition(); - var shipSizeLessOne = _shipSize[shipNumber] - 1; + var (start, delta) = random.NextShipPosition(); + var shipSizeLessOne = shipSize - 1; var end = start + delta * shipSizeLessOne; if (delta != 0 && end.IsInRange) { @@ -445,9 +454,12 @@ internal record struct Position(Coordinate X, Coordinate Y) internal record struct Coordinate(int Value) { + public const int MinValue = 0; + public const int MaxValue = 9; + public static IEnumerable Range => Enumerable.Range(0, 10).Select(v => new Coordinate(v)); - public bool IsInRange => Value is >= 0 and <= 9; + public bool IsInRange => Value is >= MinValue and <= MaxValue; public static Coordinate Create(float value) => new((int)value - 1); @@ -470,8 +482,8 @@ internal record struct Coordinate(int Value) public Coordinate BringIntoRange(IRandom random) => Value switch { - < 0 => new(0 + (int)random.NextFloat(2.5F)), - > 9 => new(9 - (int)random.NextFloat(2.5F)), + < MinValue => new(MinValue + (int)random.NextFloat(2.5F)), + > MaxValue => new(MaxValue - (int)random.NextFloat(2.5F)), _ => this }; From f5b2fe6e8a293ca066d65c6f4103ce0448c2faf1 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Mon, 27 Mar 2023 08:10:43 +1100 Subject: [PATCH 105/141] Add Ship type --- 77_Salvo/csharp/Game.cs | 211 +++++++++++++++++++++++++++------------- 1 file changed, 141 insertions(+), 70 deletions(-) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index bcc16477..ddf9aa13 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -1,3 +1,4 @@ +using System.Collections.Immutable; using Games.Common.Randomness; namespace Salvo; @@ -21,84 +22,26 @@ internal class Game { _io.Write(Streams.Title); -L1040: var humanGrid = new Grid(); var hitTurnRecord = new int[13]; var shots = new Position[8]; var temp = new Position[13]; var hitShipValue = new float[13]; -L1060: for (var i = 1; i <= 12; i++) + + for (var i = 1; i <= 12; i++) { -L1070: hitTurnRecord[i] = -1; -L1080: hitShipValue[i] = -1; - } -L1190: var computerGrid = new Grid(); -L1240: for (var K = 3; K >= 0; K--) - { -L1250: var shipGenerationAttempts=0; -L1260: var (start, delta) = _random.GetRandomShipPositionInRange(_shipSize[K]); -L1340: shipGenerationAttempts++; -L1350: if (shipGenerationAttempts>25) { goto L1190; } - // determine ship position -L1360: for (var i = 0; i < _shipSize[K]; i++) - { - temp[_shipFirstIndex[K] + i] = start + delta * i; - } -L1400: var firstIndex=_shipFirstIndex[K]; - // detect proximity to previous ships - for (var i = firstIndex; i <= firstIndex+_shipSize[K] - 1; i++) - { -L1415: if (firstIndex<2) { continue; } // Only true for the Battleship -L1420: for (var j = 1; j <= firstIndex-1; j++) - { -L1430: if (temp[j].DistanceTo(temp[i]) < 3.59) { goto L1260; } - } - } - // put ship on board -L1460: for (var i = firstIndex; i <= firstIndex + _shipSize[K] - 1; i++) - { - computerGrid[temp[i]] = _shipValue[K]; - } - } - _io.WriteLine("ENTER POSITION FOR..."); - _io.WriteLine("BATTLESHIP"); - for (var i = 1; i <= 5; i++) - { - humanGrid[_io.ReadPosition()] = 3; - } - _io.WriteLine("CRUISER"); - for (var i = 1; i <= 3; i++) - { - humanGrid[_io.ReadPosition()] = 2; - } - _io.WriteLine("DESTROYER"); - for (var i = 1; i <= 2; i++) - { - humanGrid[_io.ReadPosition()] = 1; - } - _io.WriteLine("DESTROYER"); - for (var i = 1; i <= 2; i++) - { - humanGrid[_io.ReadPosition()] = 0.5F; + hitTurnRecord[i] = -1; + hitShipValue[i] = -1; } + var computerGrid = new Grid().PositionShips(_random); + var humanGrid = new Grid().PositionShips(_io); + var startResponse = _io.ReadString("DO YOU WANT TO START"); while (startResponse == "WHERE ARE YOUR SHIPS?") { - _io.WriteLine("BATTLESHIP"); - for (var i = 1; i <= 5; i++) + foreach (var ship in computerGrid.Ships) { - _io.WriteLine(temp[i]); + _io.WriteLine(ship); } - _io.WriteLine("CRUISER"); - _io.WriteLine(temp[6]); - _io.WriteLine(temp[7]); - _io.WriteLine(temp[8]); - _io.WriteLine("DESTROYER"); - _io.WriteLine(temp[9]); - _io.WriteLine(temp[10]); - _io.WriteLine("DESTROYER"); - _io.WriteLine(temp[11]); - _io.WriteLine(temp[12]); - startResponse = _io.ReadString("DO YOU WANT TO START"); } L1890: var turnNumber=0; @@ -109,7 +52,7 @@ L1950: if (startResponse != "YES") { goto L1990; } L1960: turnNumber++; L1970: _io.WriteLine(); L1980: _io.WriteLine($"TURN {turnNumber}"); -L1990: var maxShotCount=0; +L1990: var maxShotCount = 0; L2000: for (var shipValue = .5F; shipValue <= 3; shipValue += .5F) { foreach (var position in Position.All) @@ -345,6 +288,73 @@ L4230: goto L3380; } } +internal abstract class Ship +{ + private readonly List _positions = new(); + + internal virtual string Name => GetType().Name; + internal abstract int Shots { get; } + internal abstract int Size { get; } + internal abstract float Value { get; } + internal IEnumerable Positions => _positions; + internal bool IsAfloat => _positions.Count > 0; + + internal void SetPositionInRange(IRandom random) + { + var (start, delta) = random.GetRandomShipPositionInRange(Size); + for (var i = 0; i < Size; i++) + { + _positions[i] = start + delta * i; + } + } + + internal void GetPosition(IReadWrite io, Grid grid) + { + io.WriteLine(Name); + for (var i = 0; i < Size; i++) + { + var position = io.ReadPosition(); + _positions[i] = position; + grid[position] = Value; + } + } + + internal bool IsHit(Position position) => _positions.Remove(position); + + internal float DistanceTo(Ship other) + => _positions.SelectMany(a => other._positions.Select(b => a.DistanceTo(b))).Min(); + + public override string ToString() + => string.Join(Environment.NewLine, _positions.Select(p => p.ToString()).Prepend(Name)); +} + +internal sealed class Battleship : Ship +{ + internal override int Shots => 3; + internal override int Size => 5; + internal override float Value => 3; +} + +internal sealed class Cruiser : Ship +{ + internal override int Shots => 2; + internal override int Size => 3; + internal override float Value => 2; +} + +internal sealed class Destroyer : Ship +{ + internal Destroyer(string nameIndex) + { + Name = $"{base.Name}<{nameIndex}>"; + } + + internal override string Name { get; } + internal override int Shots => 1; + internal override int Size => 2; + internal override float Value => Name.EndsWith("") ? 1 : 0.5F; +} + internal static class RandomExtensions { internal static (Position, Offset) NextShipPosition(this IRandom random) @@ -378,6 +388,67 @@ internal static class RandomExtensions internal class Grid { + private readonly List _ships = new() + { + new Battleship(), + new Cruiser(), + new Destroyer("A"), + new Destroyer("B") + }; + + internal IEnumerable Ships => _ships.AsEnumerable(); + + internal Grid PositionShips(IRandom random) + { + while (true) + { + var allPositioned = false; + for (var i = 0; i < _ships.Count; i++) + { + if (!TryPositionShip(i)) { break; } + if (i == _ships.Count - 1) { allPositioned = true; } + } + if (allPositioned) { break; } + } + + foreach (var ship in _ships) + { + foreach (var position in ship.Positions) + { + this[position] = ship.Value; + } + } + + return this; + + bool TryPositionShip(int i) + { + while (true) + { + var shipGenerationAttempts = 0; + _ships[i].SetPositionInRange(random); + if (i == 0) { return true; } + shipGenerationAttempts++; + if (shipGenerationAttempts > 25) { return false; } + for (var j = 0; j < i; j++) + { + if (_ships[i].DistanceTo(_ships[j]) >= 3.59) { return true; } + } + } + } + } + + internal Grid PositionShips(IReadWrite io) + { + io.WriteLine("ENTER POSITION FOR..."); + foreach (var ship in _ships) + { + ship.GetPosition(io, this); + } + + return this; + } + private readonly float[,] _positions = new float[10, 10]; public float this[Position position] @@ -438,8 +509,8 @@ internal record struct Position(Coordinate X, Coordinate Y) } } - internal double DistanceTo(Position other) - => Math.Sqrt((X - other.X) * (X - other.Y) + (Y - other.Y) * (Y - other.Y)); + internal float DistanceTo(Position other) + => (float)Math.Sqrt((X - other.X) * (X - other.Y) + (Y - other.Y) * (Y - other.Y)); internal Position BringIntoRange(IRandom random) => IsInRange ? this : new(X.BringIntoRange(random), Y.BringIntoRange(random)); From 192a6e7e6263451c351b20cc01d5b1d16d56ac17 Mon Sep 17 00:00:00 2001 From: Jack Boyce Date: Sun, 2 Apr 2023 00:02:24 -0700 Subject: [PATCH 106/141] remove author name from header --- 84_Super_Star_Trek/python/superstartrek.py | 1 - 1 file changed, 1 deletion(-) diff --git a/84_Super_Star_Trek/python/superstartrek.py b/84_Super_Star_Trek/python/superstartrek.py index 21e03e45..2a86efba 100644 --- a/84_Super_Star_Trek/python/superstartrek.py +++ b/84_Super_Star_Trek/python/superstartrek.py @@ -8,7 +8,6 @@ **** LEEDOM - APRIL & DECEMBER 1974, **** WITH A LITTLE HELP FROM HIS FRIENDS . . . -Python translation by Jack Boyce - February 2021 Output is identical to BASIC version except for a few fixes (as noted, search `bug`) and minor cleanup. """ From b5d573f075f280233c772ffefa3fb0bbbb033ec0 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Tue, 4 Apr 2023 17:29:50 +1000 Subject: [PATCH 107/141] More use of grid --- 77_Salvo/csharp/Game.cs | 116 ++++++++++++++++------------------------ 1 file changed, 46 insertions(+), 70 deletions(-) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index ddf9aa13..14504304 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -5,10 +5,6 @@ namespace Salvo; internal class Game { - private static readonly float[] _shipValue = new[] { 0.5F, 1, 2, 3 }; - private static readonly int[] _shipFirstIndex = new[] { 11, 9, 6, 1 }; - private static readonly int[] _shipSize = new[] { 2, 2, 3, 5 }; - private readonly IReadWrite _io; private readonly IRandom _random; @@ -34,7 +30,6 @@ internal class Game } var computerGrid = new Grid().PositionShips(_random); var humanGrid = new Grid().PositionShips(_io); - var startResponse = _io.ReadString("DO YOU WANT TO START"); while (startResponse == "WHERE ARE YOUR SHIPS?") { @@ -52,30 +47,14 @@ L1950: if (startResponse != "YES") { goto L1990; } L1960: turnNumber++; L1970: _io.WriteLine(); L1980: _io.WriteLine($"TURN {turnNumber}"); -L1990: var maxShotCount = 0; -L2000: for (var shipValue = .5F; shipValue <= 3; shipValue += .5F) - { - foreach (var position in Position.All) - { - if (humanGrid[position] == shipValue) - { - maxShotCount+=(int)(shipValue+.5F); - break; - } - } - } +L1990: var maxShotCount = humanGrid.Ships.Sum(s => s.Shots); L2090: for (var i = 1; i <= 7; i++) { shots[i] = temp[i] = 0; } -L2150: var untriedSquareCount=0; - foreach (var position in Position.All) - { - if (computerGrid[position] <= 10) { untriedSquareCount++; } - } L2220: _io.WriteLine($"YOU HAVE {maxShotCount} SHOTS."); if (maxShotCount == 0) { goto L2270; } -L2230: if (maxShotCount > untriedSquareCount) +L2230: if (maxShotCount > computerGrid.UntriedSquareCount) { _io.WriteLine("YOU HAVE MORE SHOTS THAN THERE ARE BLANK SQUARES."); L2250: goto L2890; @@ -111,32 +90,15 @@ L2620: if (startResponse == "YES") { goto L2670; } L2640: turnNumber++; L2650: _io.WriteLine(); L2660: _io.WriteLine($"TURN {turnNumber}"); -L2670: maxShotCount = 0; -L2680: for (var shipValue = .5F; shipValue <= 3; shipValue += .5F) - { - foreach (var position in Position.All) - { - if (computerGrid[position] == shipValue) - { - maxShotCount += (int)(shipValue+.5F); - break; - } - } - } -L2770: untriedSquareCount=0; - foreach (var position in Position.All) - { - if (computerGrid[position]<=10) { untriedSquareCount++; } - } +L2670: maxShotCount = computerGrid.Ships.Sum(s => s.Shots); L2840: _io.WriteLine($"I HAVE {maxShotCount} SHOTS."); -L2850: if (untriedSquareCount>maxShotCount) { goto L2880; } +L2850: if (humanGrid.UntriedSquareCount > maxShotCount) { goto L2880; } L2860: _io.WriteLine("I HAVE MORE SHOTS THAN BLANK SQUARES."); -L2870: goto L2270; +L2270: _io.WriteLine("I HAVE WON."); + return; L2880: if (maxShotCount != 0) { goto L2960; } L2890: _io.WriteLine("YOU HAVE WON."); L2900: return; -L2270: _io.WriteLine("I HAVE WON."); - return; L2960: for (var i = 1; i <= 12; i++) @@ -304,20 +266,11 @@ internal abstract class Ship var (start, delta) = random.GetRandomShipPositionInRange(Size); for (var i = 0; i < Size; i++) { - _positions[i] = start + delta * i; + _positions.Add(start + delta * i); } } - internal void GetPosition(IReadWrite io, Grid grid) - { - io.WriteLine(Name); - for (var i = 0; i < Size; i++) - { - var position = io.ReadPosition(); - _positions[i] = position; - grid[position] = Value; - } - } + internal void AddPosition(Position position) => _positions.Add(position); internal bool IsHit(Position position) => _positions.Remove(position); @@ -395,7 +348,21 @@ internal class Grid new Destroyer("A"), new Destroyer("B") }; + private readonly Dictionary _shots = new(); + public float this[Position position] + { + get => _shots.TryGetValue(position, out var value) + ? value + 10 + : _ships.FirstOrDefault(s => s.Positions.Contains(position))?.Value ?? 0; + set + { + _ = _ships.FirstOrDefault(s => s.IsHit(position)); + _shots[position] = (int)value - 10; + } + } + + internal int UntriedSquareCount => 100 - _shots.Count; internal IEnumerable Ships => _ships.AsEnumerable(); internal Grid PositionShips(IRandom random) @@ -439,23 +406,10 @@ internal class Grid } internal Grid PositionShips(IReadWrite io) - { - io.WriteLine("ENTER POSITION FOR..."); - foreach (var ship in _ships) - { - ship.GetPosition(io, this); - } - + { + io.ReadShipPositions(this); return this; } - - private readonly float[,] _positions = new float[10, 10]; - - public float this[Position position] - { - get => _positions[position.X, position.Y]; - set => _positions[position.X, position.Y] = value; - } } internal static class IOExtensions @@ -473,6 +427,28 @@ internal static class IOExtensions io.WriteLine("ILLEGAL, ENTER AGAIN."); } } + + + internal static void ReadShipPositions(this IReadWrite io, Grid grid) + { + io.WriteLine("ENTER POSITION FOR..."); + foreach (var ship in grid.Ships) + { + io.ReadPosition(ship, grid); + } + } + + internal static void ReadPosition(this IReadWrite io, Ship ship, Grid grid) + { + io.WriteLine(ship.Name); + for (var i = 0; i < ship.Size; i++) + { + var position = io.ReadPosition(); + ship.AddPosition(position); + grid[position] = ship.Value; + } + } + } internal record struct Position(Coordinate X, Coordinate Y) From 223422b94ac9e8ec73ad86ad2f8faf21172bf50c Mon Sep 17 00:00:00 2001 From: kbrannen Date: Fri, 7 Apr 2023 22:57:07 -0500 Subject: [PATCH 108/141] added 06_banner for perl --- 06_Banner/perl/banner.pl | 150 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 06_Banner/perl/banner.pl diff --git a/06_Banner/perl/banner.pl b/06_Banner/perl/banner.pl new file mode 100644 index 00000000..2524ef00 --- /dev/null +++ b/06_Banner/perl/banner.pl @@ -0,0 +1,150 @@ +#!/usr/bin/perl + +# Banner program in Perl +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +sub print_lines +{ + my $lines = shift; + print "\n" x $lines; +} + +# each letter is made of 7 slices (or rows); +# the initial & unused 0 is to allow for Perl arrays being 0-based +# but allow the algorithm to be 1-based (like Basic arrays); +# the numbers are essentially the dots/columns per slice in powers of 2. +my %data = ( + " " => [ 0, 0, 0, 0, 0, 0, 0, 0 ], + "." => [ 0, 1, 1, 129, 449, 129, 1, 1 ], + "!" => [ 0, 1, 1, 1, 384, 1, 1, 1 ], + "=" => [ 0, 41, 41, 41, 41, 41, 41, 41 ], + "?" => [ 0, 5, 3, 2, 354, 18, 11, 5 ], + "*" => [ 0, 69, 41, 17, 512, 17, 41, 69 ], + "0" => [ 0, 57, 69, 131, 258, 131, 69, 57 ], + "1" => [ 0, 0, 0, 261, 259, 512, 257, 257 ], + "2" => [ 0, 261, 387, 322, 290, 274, 267, 261 ], + "3" => [ 0, 66, 130, 258, 274, 266, 150, 100 ], + "4" => [ 0, 33, 49, 41, 37, 35, 512, 33 ], + "5" => [ 0, 160, 274, 274, 274, 274, 274, 226 ], + "6" => [ 0, 194, 291, 293, 297, 305, 289, 193 ], + "7" => [ 0, 258, 130, 66, 34, 18, 10, 8 ], + "8" => [ 0, 69, 171, 274, 274, 274, 171, 69 ], + "9" => [ 0, 263, 138, 74, 42, 26, 10, 7 ], + "A" => [ 0, 505, 37, 35, 34, 35, 37, 505 ], + "B" => [ 0, 512, 274, 274, 274, 274, 274, 239 ], + "C" => [ 0, 125, 131, 258, 258, 258, 131, 69 ], + "D" => [ 0, 512, 258, 258, 258, 258, 131, 125 ], + "E" => [ 0, 512, 274, 274, 274, 274, 258, 258 ], + "F" => [ 0, 512, 18, 18, 18, 18, 2, 2 ], + "G" => [ 0, 125, 131, 258, 258, 290, 163, 101 ], + "H" => [ 0, 512, 17, 17, 17, 17, 17, 512 ], + "I" => [ 0, 258, 258, 258, 512, 258, 258, 258 ], + "J" => [ 0, 65, 129, 257, 257, 257, 129, 128 ], + "K" => [ 0, 512, 17, 17, 41, 69, 131, 258 ], + "L" => [ 0, 512, 257, 257, 257, 257, 257, 257 ], + "M" => [ 0, 512, 7, 13, 25, 13, 7, 512 ], + "N" => [ 0, 512, 7, 9, 17, 33, 193, 512 ], + "O" => [ 0, 125, 131, 258, 258, 258, 131, 125 ], + "P" => [ 0, 512, 18, 18, 18, 18, 18, 15 ], + "Q" => [ 0, 125, 131, 258, 258, 322, 131, 381 ], + "R" => [ 0, 512, 18, 18, 50, 82, 146, 271 ], + "S" => [ 0, 69, 139, 274, 274, 274, 163, 69 ], + "T" => [ 0, 2, 2, 2, 512, 2, 2, 2 ], + "U" => [ 0, 128, 129, 257, 257, 257, 129, 128 ], + "V" => [ 0, 64, 65, 129, 257, 129, 65, 64 ], + "W" => [ 0, 256, 257, 129, 65, 129, 257, 256 ], + "X" => [ 0, 388, 69, 41, 17, 41, 69, 388 ], + "Y" => [ 0, 8, 9, 17, 481, 17, 9, 8 ], + "Z" => [ 0, 386, 322, 290, 274, 266, 262, 260 ], +); + +my ($horz, $vert, $center, $char, $msg) = (0, 0, '', '', ''); + +# get args to run with +while ($horz < 1) +{ + print "HORIZONTAL (1 or more): "; + chomp($horz = <>); + $horz = int($horz); +} + +while ($vert < 1) +{ + print "VERTICAL (1 or more): "; + chomp($vert = <>); + $vert = int($vert); +} + +print "CENTERED (Y/N): "; +chomp($center = <>); +$center = ($center =~ m/^Y/i) ? 1 : 0; + +# note you can enter multiple chars and the program will do the right thing +# thanks to the length() calls below, which was in the original Basic +print "CHARACTER TO PRINT (TYPE 'ALL' IF YOU WANT CHARACTER BEING PRINTED): "; +chomp($char = uc(<>)); + +while (!$msg) +{ + print "STATEMENT: "; + chomp($msg = uc(<>)); +} + +print "SET PAGE TO PRINT, HIT RETURN WHEN READY"; +$_ = <>; +print_lines(2 * $horz); + +# print the message +for my $letter ( split(//, $msg) ) +{ + if (!exists($data{$letter})) + { + die "Cannot use letter '$letter'!"; + } + my @s = @{$data{$letter}}; + #if ($letter eq " ") { print_lines(7 * $horz); next; } + + my $print_letter = ($char eq "ALL") ? $letter : $char; + for my $slice (1 .. 7) + { + my (@j, @f); + for (my $k = 8; $k >= 0; $k--) + { + if (2**$k < $s[$slice]) + { + $j[9 - $k] = 1; + $s[$slice] = $s[$slice] - 2**$k; + if ($s[$slice] == 1) + { + $f[$slice] = 9 - $k; + } + } + else + { + $j[9 - $k] = 0; + } + } + + for my $t1 (1 .. $horz) + { + print " " x int((63 - 4.5 * $vert) * $center / (length($print_letter)) + 1); + for my $b (1 .. (defined($f[$slice]) ? $f[$slice] : 0)) + { + my $str = $j[$b] ? $print_letter : (" " x length($print_letter)); + print $str for (1 .. $vert); + } + print "\n"; + } + } + + # space between letters + print_lines(2 * $horz); +} + +# while in the original code, this seems pretty excessive +#print_lines(75); + +exit(0); From 69240e50a0b175ad1d46b75f5f00fd226fa2c927 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Wed, 12 Apr 2023 07:49:10 +1000 Subject: [PATCH 109/141] More improvments to Ships and Grid --- 77_Salvo/csharp/Game.cs | 189 ++++++++++++++++++++++------------------ 1 file changed, 105 insertions(+), 84 deletions(-) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index 14504304..312796db 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -28,8 +28,8 @@ internal class Game hitTurnRecord[i] = -1; hitShipValue[i] = -1; } - var computerGrid = new Grid().PositionShips(_random); - var humanGrid = new Grid().PositionShips(_io); + var computerGrid = new Grid(_random); + var humanGrid = new Grid(_io); var startResponse = _io.ReadString("DO YOU WANT TO START"); while (startResponse == "WHERE ARE YOUR SHIPS?") { @@ -254,15 +254,16 @@ internal abstract class Ship { private readonly List _positions = new(); - internal virtual string Name => GetType().Name; - internal abstract int Shots { get; } - internal abstract int Size { get; } - internal abstract float Value { get; } - internal IEnumerable Positions => _positions; - internal bool IsAfloat => _positions.Count > 0; - - internal void SetPositionInRange(IRandom random) + protected Ship(IReadWrite io, string? nameSuffix = null) { + Name = GetType().Name + nameSuffix; + _positions = io.ReadPositions(Name, Size).ToList(); + } + + protected Ship(IRandom random, string? nameSuffix = null) + { + Name = GetType().Name + nameSuffix; + var (start, delta) = random.GetRandomShipPositionInRange(Size); for (var i = 0; i < Size; i++) { @@ -270,7 +271,12 @@ internal abstract class Ship } } - internal void AddPosition(Position position) => _positions.Add(position); + internal string Name { get; } + internal abstract int Shots { get; } + internal abstract int Size { get; } + internal abstract float Value { get; } + internal IEnumerable Positions => _positions; + internal bool IsAfloat => _positions.Count > 0; internal bool IsHit(Position position) => _positions.Remove(position); @@ -283,6 +289,16 @@ internal abstract class Ship internal sealed class Battleship : Ship { + internal Battleship(IReadWrite io) + : base(io) + { + } + + internal Battleship(IRandom random) + : base(random) + { + } + internal override int Shots => 3; internal override int Size => 5; internal override float Value => 3; @@ -290,6 +306,16 @@ internal sealed class Battleship : Ship internal sealed class Cruiser : Ship { + internal Cruiser(IReadWrite io) + : base(io) + { + } + + internal Cruiser(IRandom random) + : base(random) + { + } + internal override int Shots => 2; internal override int Size => 3; internal override float Value => 2; @@ -297,12 +323,16 @@ internal sealed class Cruiser : Ship internal sealed class Destroyer : Ship { - internal Destroyer(string nameIndex) + internal Destroyer(string nameIndex, IReadWrite io) + : base(io, $"<{nameIndex}>") + { + } + + internal Destroyer(string nameIndex, IRandom random) + : base(random, $"<{nameIndex}>") { - Name = $"{base.Name}<{nameIndex}>"; } - internal override string Name { get; } internal override int Shots => 1; internal override int Size => 2; internal override float Value => Name.EndsWith("") ? 1 : 0.5F; @@ -341,15 +371,65 @@ internal static class RandomExtensions internal class Grid { - private readonly List _ships = new() - { - new Battleship(), - new Cruiser(), - new Destroyer("A"), - new Destroyer("B") - }; + private readonly List _ships; private readonly Dictionary _shots = new(); + internal Grid() + { + _ships = new(); + } + + internal Grid(IReadWrite io) + { + io.WriteLine("ENTER POSITION FOR..."); + _ships = new() + { + new Battleship(io), + new Cruiser(io), + new Destroyer("A", io), + new Destroyer("B", io) + }; + } + + internal Grid(IRandom random) + { + _ships = new(); + while (true) + { + _ships.Add(new Battleship(random)); + if (TryPositionShip(() => new Cruiser(random)) && + TryPositionShip(() => new Destroyer("A", random)) && + TryPositionShip(() => new Destroyer("B", random))) + { + break; + } + _ships.Clear(); + } + + foreach (var ship in _ships) + { + foreach (var position in ship.Positions) + { + this[position] = ship.Value; + } + } + + bool TryPositionShip(Func shipFactory) + { + while (true) + { + var shipGenerationAttempts = 0; + var ship = shipFactory.Invoke(); + shipGenerationAttempts++; + if (shipGenerationAttempts > 25) { return false; } + foreach (var previousShip in _ships) + { + if (ship.DistanceTo(previousShip) >= 3.59) { return true; } + } + } + } + } + public float this[Position position] { get => _shots.TryGetValue(position, out var value) @@ -364,52 +444,6 @@ internal class Grid internal int UntriedSquareCount => 100 - _shots.Count; internal IEnumerable Ships => _ships.AsEnumerable(); - - internal Grid PositionShips(IRandom random) - { - while (true) - { - var allPositioned = false; - for (var i = 0; i < _ships.Count; i++) - { - if (!TryPositionShip(i)) { break; } - if (i == _ships.Count - 1) { allPositioned = true; } - } - if (allPositioned) { break; } - } - - foreach (var ship in _ships) - { - foreach (var position in ship.Positions) - { - this[position] = ship.Value; - } - } - - return this; - - bool TryPositionShip(int i) - { - while (true) - { - var shipGenerationAttempts = 0; - _ships[i].SetPositionInRange(random); - if (i == 0) { return true; } - shipGenerationAttempts++; - if (shipGenerationAttempts > 25) { return false; } - for (var j = 0; j < i; j++) - { - if (_ships[i].DistanceTo(_ships[j]) >= 3.59) { return true; } - } - } - } - } - - internal Grid PositionShips(IReadWrite io) - { - io.ReadShipPositions(this); - return this; - } } internal static class IOExtensions @@ -428,27 +462,14 @@ internal static class IOExtensions } } - - internal static void ReadShipPositions(this IReadWrite io, Grid grid) + internal static IEnumerable ReadPositions(this IReadWrite io, string shipName, int shipSize) { - io.WriteLine("ENTER POSITION FOR..."); - foreach (var ship in grid.Ships) + io.WriteLine(shipName); + for (var i = 0; i < shipSize; i++) { - io.ReadPosition(ship, grid); + yield return io.ReadPosition(); } } - - internal static void ReadPosition(this IReadWrite io, Ship ship, Grid grid) - { - io.WriteLine(ship.Name); - for (var i = 0; i < ship.Size; i++) - { - var position = io.ReadPosition(); - ship.AddPosition(position); - grid[position] = ship.Value; - } - } - } internal record struct Position(Coordinate X, Coordinate Y) From a5584b401481b26ceaf575a7aeff612209cf816a Mon Sep 17 00:00:00 2001 From: kbrannen Date: Thu, 20 Apr 2023 12:23:44 -0500 Subject: [PATCH 110/141] Added 2 versions of the perl 07-basketball game. Also updated the README. --- 07_Basketball/perl/README.md | 18 ++ 07_Basketball/perl/basketball-orig.pl | 415 ++++++++++++++++++++++++++ 07_Basketball/perl/basketball.pl | 394 ++++++++++++++++++++++++ 3 files changed, 827 insertions(+) create mode 100755 07_Basketball/perl/basketball-orig.pl create mode 100755 07_Basketball/perl/basketball.pl diff --git a/07_Basketball/perl/README.md b/07_Basketball/perl/README.md index e69c8b81..51373c0c 100644 --- a/07_Basketball/perl/README.md +++ b/07_Basketball/perl/README.md @@ -1,3 +1,21 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) Conversion to [Perl](https://www.perl.org/) + +There are two version of the code here, a "faithful" translation (basketball-orig.pl) and +a "modern" translation (basketball.pl). The main difference between the 2 are is that the +faithful translation has 3 GOTOs in it while the modern version has no GOTO. I have added +a "TIME" print when the score is shown so the Clock is visible. Halftime is at "50" and +end of game is at 100 (per the Basic code). + +The 3 GOTOs in the faitful version are because of the way the original code jumped into +the "middle of logic" that has no obivious way to avoid ... that I can see, at least while +still maintaining something of the look and structure of the original Basic. + +The modern version avoided the GOTOs by restructuring the program in the 2 "play()" subs. +Despite the change, this should play the same way as the faithful version. + +All of the percentages remain the same. If writing this from scratch, we really should +have only a single play() sub which uses the same code for both teams, which would also +make the game more fair ... but that wasn't done so the percent edge to Darmouth has been +maintained here. diff --git a/07_Basketball/perl/basketball-orig.pl b/07_Basketball/perl/basketball-orig.pl new file mode 100755 index 00000000..14627ffc --- /dev/null +++ b/07_Basketball/perl/basketball-orig.pl @@ -0,0 +1,415 @@ +#!/usr/bin/perl + +# Basketball program in Perl +# This is fairly faithful translation from the original Basic. +# This becomes apparent because there are actually 3 GOTOs still present +# because of the way the original code jumped into the "middle of logic" +# that has no obivious way to avoid ... that I can see. +# For better structure and no GOTOs, see the other version of this program. +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# globals +my $Defense=0; # dartmouth defense value +my $Opponent; # name of opponent +my @Score = (0, 0); # scores, dart is [0], opponent is [1] +my $Player = 0; # player, 0 = dart, 1 = opp +my $Timer = 0; # time tick, 100 ticks per game, 50 is end of first half, if tie at end then back to T=93 +my $DoPlay = 1; # true if game is still being played +my $ConTeam; # controlling team, "dart" or "opp" +my $ShotType = 0; # current shot type + + +print "\n"; +print " " x 31, "BASKETBALL"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"; +print "\n\n\n"; + +print "THIS IS DARTMOUTH COLLEGE BASKETBALL. YOU WILL BE DARTMOUTH\n"; +print "CAPTAIN AND PLAYMAKER. CALL SHOTS AS FOLLOWS:\n"; +print " 1. LONG (30 FT.) JUMP SHOT;\n"; +print " 2. SHORT (15 FT.) JUMP SHOT;\n"; +print " 3. LAY UP;\n"; +print " 4. SET SHOT.\n"; +print "BOTH TEAMS WILL USE THE SAME DEFENSE. CALL DEFENSE AS FOLLOWS:\n"; +print " 6. PRESS;\n"; +print " 6.5 MAN-TO MAN;\n"; +print " 7. ZONE;\n"; +print " 7.5 NONE.\n"; +print "TO CHANGE DEFENSE, JUST TYPE 0 AS YOUR NEXT SHOT.\n\n"; +get_defense(); +print "\n"; +print "CHOOSE YOUR OPPONENT: "; +chomp($Opponent = <>); + +$ConTeam = center_jump(); +while ($DoPlay) +{ + print "\n"; + if ($ConTeam eq "dart") + { + $Player = 0; + get_your_shot(); + dartmouth_play(); + } + else + { + opponent_play(); + } + if ($Timer >= 100) + { + check_end_game(); + last if (!$DoPlay); + $Timer = 93; + $ConTeam = center_jump(); + } +} +exit(0); + +############################################################### + +sub dartmouth_play +{ + if ($ShotType == 1 || $ShotType == 2) + { + $Timer++; + if ($Timer == 50) + { + end_first_half(); + return; + } + if ($Timer == 92) + { + two_min_left(); + } + + print "JUMP SHOT\n"; + if (rand(1) <= 0.341 * $Defense / 8) + { + print "SHOT IS GOOD.\n"; + dartmouth_score(); + $ConTeam = "opp"; + return; + } + + if (rand(1) <= 0.682*$Defense/8) + { + print "SHOT IS OFF TARGET.\n"; + if ($Defense/6*rand(1) > 0.45) + { + print "REBOUND TO ", $Opponent, "\n"; + $ConTeam = "opp"; + return; + } + + print "DARTMOUTH CONTROLS THE REBOUND.\n"; + if (rand(1) <= 0.4) { goto L1300; } + if ($Defense == 6) + { + if (rand(1) <= 0.6) + { + print "PASS STOLEN BY $Opponent, EASY LAYUP.\n"; + opp_score(); + $ConTeam = "dart"; return; + return; + } + } + print "BALL PASSED BACK TO YOU.\n"; + $ConTeam = "dart"; + return; + } + + if (rand(1) <= 0.782*$Defense/8) + { + print "SHOT IS BLOCKED. BALL CONTROLLED BY "; # no NL + if (rand(1) > 0.5) + { + print "$Opponent.\n"; + $ConTeam = "opp"; + return; + } + else + { + print "DARTMOUTH.\n"; + $ConTeam = "dart"; + return; + } + } + + if (rand(1) > 0.843*$Defense/8) + { + print "CHARGING FOUL. DARTMOUTH LOSES BALL.\n"; + $ConTeam = "opp"; + return; + } + else + { + print "SHOOTER IS FOULED. TWO SHOTS.\n"; + foul_shooting(); + $ConTeam = "opp"; + return; + } + } + + L1300: + while (1) + { + $Timer++; + if ($Timer == 50) + { + end_first_half(); + return; + } + if ($Timer == 92) { two_min_left(); } + if ($ShotType == 0) + { + get_defense(); + return; + } + print '', ($ShotType > 3 ? "SET SHOT." : "LAY UP."), "\n"; + if (7 / $Defense * rand(1) <= 0.4) + { + print "SHOT IS GOOD. TWO POINTS.\n"; + dartmouth_score(); + $ConTeam = "opp"; + return; + } + + if (7 / $Defense * rand(1) <= 0.7) + { + print "SHOT IS OFF THE RIM.\n"; + if (rand(1) <= 0.667) + { + print "$Opponent CONTROLS THE REBOUND.\n"; + $ConTeam = "opp"; + return; + } + + print "DARTMOUTH CONTROLS THE REBOUND.\n"; + next if (rand(1) <= 0.4); + + print "BALL PASSED BACK TO YOU.\n"; + $ConTeam = "dart"; + return; + } + + if (7 / $Defense * rand(1) <= 0.875) + { + print "SHOOTER FOULED. TWO SHOTS.\n"; + foul_shooting(); + $ConTeam = "opp"; + return; + } + + if (7 / $Defense * rand(1) <= 0.925) + { + print "SHOT BLOCKED. $Opponent\'S BALL.\n"; + $ConTeam = "opp"; + return; + } + + print "CHARGING FOUL. DARTMOUTH LOSES THE BALL.\n"; + $ConTeam = "opp"; + return; + } +} + +sub get_defense +{ + $Defense = 0; + while ($Defense < 6 || $Defense > 7.5) + { + print "YOUR NEW DEFENSIVE ALLIGNMENT IS (6, 6.5, 7. 7.5): "; + chomp($Defense = <>); + ($Defense) =~ m/(\d(\.\d)?)/; + } +} + +sub opponent_play +{ + $Player = 1; + $Timer++; + if ($Timer == 50) + { + end_first_half(); + $ConTeam = center_jump(); + return; + } + + print "\n"; + while (1) + { + my $shot = 10.0 / 4 * rand(1) + 1; + if ($shot <= 2.0) + { + print "JUMP SHOT.\n"; + if (8.0 / $Defense * rand(1) <= 0.35) + { + print "SHOT IS GOOD.\n"; + opp_score(); + $ConTeam = "dart"; + return; + } + + if (8.0 / $Defense * rand(1) <= 0.75) + { + print "SHOT IS OFF RIM.\n"; + + L3110: + if ($Defense / 6.0 * rand(1) <= 0.5) + { + print "DARTMOUTH CONTROLS THE REBOUND.\n"; + $ConTeam = "dart"; + return; + } + print "$Opponent CONTROLS THE REBOUND.\n"; + if ($Defense == 6) + { + if (rand(1) <= 0.75) + { + print "BALL STOLEN. EASY LAY UP FOR DARTMOUTH.\n"; + dartmouth_score(); + $ConTeam = "opp"; + return; + } + } + if (rand(1) <= 0.5) + { + print "PASS BACK TO $Opponent GUARD.\n"; + $ConTeam = "opp"; + return; + } + goto L3500; + } + + if (8.0 / $Defense * rand(1) <= 0.9) + { + print "PLAYER FOULED. TWO SHOTS.\n"; + foul_shooting(); + $ConTeam = "dart"; + return; + } + print "OFFENSIVE FOUL. DARTMOUTH'S BALL.\n"; + $ConTeam = "dart"; + return; + } + + L3500: + print ($shot > 3 ? "SET SHOT.\n" : "LAY UP.\n"); + if (7.0 / $Defense * rand(1) > 0.413) + { + print "SHOT IS MISSED.\n"; + { + no warnings; + goto L3110; + } + } + else + { + print "SHOT IS GOOD.\n"; + opp_score(); + $ConTeam = "dart"; + return; + } + } +} + +sub opp_score +{ + $Score[0] += 2; + print_score(); +} + +sub dartmouth_score +{ + $Score[1] += 2; + print_score(); +} + +sub print_score +{ + print "SCORE: $Score[1] TO $Score[0]\n"; + print "TIME: $Timer\n"; +} + +sub end_first_half +{ + print "\n ***** END OF FIRST HALF *****\n\n"; + print "SCORE: DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n\n"; + center_jump(); +} + +sub get_your_shot +{ + $ShotType = -1; + while ($ShotType < 0 || $ShotType > 4) + { + print "YOUR SHOT (0-4): "; + chomp($ShotType = <>); + $ShotType = int($ShotType); + if ($ShotType < 0 || $ShotType > 4) + { + print "INCORRECT ANSWER. RETYPE IT. "; + } + } +} + +sub center_jump +{ + print "CENTER JUMP\n"; + if (rand(1) <= 0.6) + { + print "$Opponent CONTROLS THE TAP.\n"; + return "opp"; + } + print "DARTMOUTH CONTROLS THE TAP.\n"; + return "dart"; +} + +sub check_end_game +{ + print "\n"; + if ($Score[1] != $Score[0]) + { + print " ***** END OF GAME *****\n"; + print "FINAL SCORE: DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n"; + $DoPlay = 0; + } + else + { + print "\n ***** END OF SECOND HALF *****\n"; + print "SCORE AT END OF REGULATION TIME:\n"; + print " DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n"; + print "BEGIN TWO MINUTE OVERTIME PERIOD\n"; + } +} + +sub two_min_left +{ + print "\n *** TWO MINUTES LEFT IN THE GAME ***\n\n"; +} + +sub foul_shooting +{ + if (rand(1) > 0.49) + { + if (rand(1) > 0.75) + { + print "BOTH SHOTS MISSED.\n"; + } + else + { + print "SHOOTER MAKES ONE SHOT AND MISSES ONE.\n"; + $Score[1 - $Player]++; + } + } + else + { + print "SHOOTER MAKES BOTH SHOTS.\n"; + $Score[1 - $Player] += 2; + } + + print_score(); +} diff --git a/07_Basketball/perl/basketball.pl b/07_Basketball/perl/basketball.pl new file mode 100755 index 00000000..a6f98041 --- /dev/null +++ b/07_Basketball/perl/basketball.pl @@ -0,0 +1,394 @@ +#!/usr/bin/perl + +# Basketball program in Perl +# While this should play the same way as the fairly faithful translation version, +# there are no GOTOs in this code. That was achieved by restructuring the program +# in the 2 *_play() subs. All of the percentages remain the same. If writing this +# from scratch, we really should have only a play() sub which uses the same code +# for both teams, but the percent edge to Darmouth has been maintained here. +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# globals +my $Defense=0; # dartmouth defense value +my $Opponent; # name of opponent +my @Score = (0, 0); # scores, dart is [0], opponent is [1] +my $Player = 0; # player, 0 = dart, 1 = opp +my $Timer = 0; # time tick, 100 ticks per game, 50 is end of first half, if tie at end then back to T=93 +my $DoPlay = 1; # true if game is still being played +my $ConTeam; # controlling team, "dart" or "opp" +my $ShotType = 0; # current shot type + + +print "\n"; +print " " x 31, "BASKETBALL"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"; +print "\n\n\n"; + +print "THIS IS DARTMOUTH COLLEGE BASKETBALL. YOU WILL BE DARTMOUTH\n"; +print "CAPTAIN AND PLAYMAKER. CALL SHOTS AS FOLLOWS:\n"; +print " 1. LONG (30 FT.) JUMP SHOT;\n"; +print " 2. SHORT (15 FT.) JUMP SHOT;\n"; +print " 3. LAY UP;\n"; +print " 4. SET SHOT.\n"; +print "BOTH TEAMS WILL USE THE SAME DEFENSE. CALL DEFENSE AS FOLLOWS:\n"; +print " 6. PRESS;\n"; +print " 6.5 MAN-TO MAN;\n"; +print " 7. ZONE;\n"; +print " 7.5 NONE.\n"; +print "TO CHANGE DEFENSE, JUST TYPE 0 AS YOUR NEXT SHOT.\n\n"; +get_defense(); +print "\n"; +print "CHOOSE YOUR OPPONENT: "; +chomp($Opponent = <>); + +$ConTeam = center_jump(); +while ($DoPlay) +{ + print "\n"; + if ($ConTeam eq "dart") + { + $Player = 0; + get_your_shot(); + dartmouth_play(); + } + else + { + $ShotType = 10.0 / 4 * rand(1) + 1; + opponent_play(); + } + if ($Timer >= 100) + { + check_end_game(); + last if (!$DoPlay); + $Timer = 93; + $ConTeam = center_jump(); + } +} +exit(0); + +############################################################### + +sub dartmouth_play +{ + $Player = 0; + print "\n"; + while (1) + { + $Timer++; + if ($Timer == 50) { end_first_half(); return; } + if ($Timer == 92) { two_min_left(); } + + if ($ShotType == 0) + { + get_defense(); + return; # for new ShotType + } + elsif ($ShotType == 1 || $ShotType == 2) + { + print "JUMP SHOT\n"; + if (rand(1) <= 0.341 * $Defense / 8) + { + print "SHOT IS GOOD.\n"; + dartmouth_score(); + last; + } + + if (rand(1) <= 0.682*$Defense/8) + { + print "SHOT IS OFF TARGET.\n"; + if ($Defense/6*rand(1) > 0.45) + { + print "REBOUND TO $Opponent\n"; + last; + } + + print "DARTMOUTH CONTROLS THE REBOUND.\n"; + if (rand(1) <= 0.4) + { + $ShotType = (rand(1) <= 0.5) ? 3 : 4; + next; + } + if ($Defense == 6) + { + if (rand(1) <= 0.6) + { + print "PASS STOLEN BY $Opponent, EASY LAYUP.\n"; + opp_score(); + next; + } + } + print "BALL PASSED BACK TO YOU.\n"; + next; + } + + if (rand(1) <= 0.782*$Defense/8) + { + print "SHOT IS BLOCKED. BALL CONTROLLED BY "; # no NL + if (rand(1) > 0.5) + { + print "$Opponent.\n"; + last; + } + else + { + print "DARTMOUTH.\n"; + next; + } + } + + if (rand(1) > 0.843*$Defense/8) + { + print "CHARGING FOUL. DARTMOUTH LOSES BALL.\n"; + last; + } + else + { + print "SHOOTER IS FOULED. TWO SHOTS.\n"; + foul_shooting(); + last; + } + } + else # elsif ($ShotType >= 3) + { + print '', ($ShotType > 3 ? "SET SHOT." : "LAY UP."), "\n"; + if (7 / $Defense * rand(1) <= 0.4) + { + print "SHOT IS GOOD. TWO POINTS.\n"; + dartmouth_score(); + last; + } + + if (7 / $Defense * rand(1) <= 0.7) + { + print "SHOT IS OFF THE RIM.\n"; + if (rand(1) <= 0.667) + { + print "$Opponent CONTROLS THE REBOUND.\n"; + last; + } + + print "DARTMOUTH CONTROLS THE REBOUND.\n"; + next if (rand(1) <= 0.4); + + print "BALL PASSED BACK TO YOU.\n"; + next; + } + + if (7 / $Defense * rand(1) <= 0.875) + { + print "SHOOTER FOULED. TWO SHOTS.\n"; + foul_shooting(); + last; + } + + if (7 / $Defense * rand(1) <= 0.925) + { + print "SHOT BLOCKED. $Opponent\'S BALL.\n"; + last; + } + + print "CHARGING FOUL. DARTMOUTH LOSES THE BALL.\n"; + last; + } + } + $ConTeam = "opp"; +} + +sub get_defense +{ + $Defense = 0; + do { + print "YOUR NEW DEFENSIVE ALLIGNMENT IS (6, 6.5, 7. 7.5): "; + chomp($Defense = <>); + ($Defense) =~ m/(\d(\.\d)?)/; + } while ($Defense < 6.0 || $Defense > 7.5) +} + +sub opponent_play +{ + $Player = 1; + print "\n"; + while (1) + { + $Timer++; + if ($Timer == 50) { end_first_half(); return; } + if ($Timer == 92) { two_min_left(); } + + if ($ShotType <= 2.0) + { + print "JUMP SHOT.\n"; + if (8.0 / $Defense * rand(1) <= 0.35) + { + print "SHOT IS GOOD.\n"; + opp_score(); + last; + } + + if (8.0 / $Defense * rand(1) <= 0.75) + { + print "SHOT IS OFF RIM.\n"; + opp_missed(); + return; # for possible new ShotType or team change + } + + if (8.0 / $Defense * rand(1) <= 0.9) + { + print "PLAYER FOULED. TWO SHOTS.\n"; + foul_shooting(); + last; + } + print "OFFENSIVE FOUL. DARTMOUTH'S BALL.\n"; + last; + } + else # ShotType >= 3 + { + print ($ShotType > 3 ? "SET SHOT.\n" : "LAY UP.\n"); + if (7.0 / $Defense * rand(1) > 0.413) + { + print "SHOT IS MISSED.\n"; + { + opp_missed(); + return; # for possible new ShotType or team change + } + } + else + { + print "SHOT IS GOOD.\n"; + opp_score(); + last; + } + } + } + $ConTeam = "dart"; +} + +sub opp_missed +{ + if ($Defense / 6.0 * rand(1) <= 0.5) + { + print "DARTMOUTH CONTROLS THE REBOUND.\n"; + $ConTeam = "dart"; + } + else + { + print "$Opponent CONTROLS THE REBOUND.\n"; + if ($Defense == 6) + { + if (rand(1) <= 0.75) + { + print "BALL STOLEN. EASY LAY UP FOR DARTMOUTH.\n"; + dartmouth_score(); + #$ConTeam = "opp"; + return; # for possible new ShotType + } + } + if (rand(1) <= 0.5) + { + print "PASS BACK TO $Opponent GUARD.\n"; + #$ConTeam = "opp"; + return; # for possible new ShotType + } + $ShotType = (rand(1) <= 0.5) ? 3 : 4; + } +} + +sub opp_score +{ + $Score[0] += 2; + print_score(); +} + +sub dartmouth_score +{ + $Score[1] += 2; + print_score(); +} + +sub print_score +{ + print "SCORE: $Score[1] TO $Score[0]\n"; + print "TIME: $Timer\n"; +} + +sub end_first_half +{ + print "\n ***** END OF FIRST HALF *****\n\n"; + print "SCORE: DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n\n"; + $ConTeam = center_jump(); +} + +sub get_your_shot +{ + $ShotType = -1; + while ($ShotType < 0 || $ShotType > 4) + { + print "YOUR SHOT (0-4): "; + chomp($ShotType = <>); + $ShotType = int($ShotType); + if ($ShotType < 0 || $ShotType > 4) + { + print "INCORRECT ANSWER. RETYPE IT. "; + } + } +} + +sub center_jump +{ + print "CENTER JUMP\n"; + if (rand(1) <= 0.6) + { + print "$Opponent CONTROLS THE TAP.\n"; + return "opp"; + } + print "DARTMOUTH CONTROLS THE TAP.\n"; + return "dart"; +} + +sub check_end_game +{ + print "\n"; + if ($Score[1] != $Score[0]) + { + print " ***** END OF GAME *****\n"; + print "FINAL SCORE: DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n"; + $DoPlay = 0; + } + else + { + print "\n ***** END OF SECOND HALF *****\n"; + print "SCORE AT END OF REGULATION TIME:\n"; + print " DARTMOUTH: $Score[1] $Opponent: $Score[0]\n\n"; + print "BEGIN TWO MINUTE OVERTIME PERIOD\n"; + } +} + +sub two_min_left +{ + print "\n *** TWO MINUTES LEFT IN THE GAME ***\n\n"; +} + +sub foul_shooting +{ + if (rand(1) > 0.49) + { + if (rand(1) > 0.75) + { + print "BOTH SHOTS MISSED.\n"; + } + else + { + print "SHOOTER MAKES ONE SHOT AND MISSES ONE.\n"; + $Score[1 - $Player]++; + } + } + else + { + print "SHOOTER MAKES BOTH SHOTS.\n"; + $Score[1 - $Player] += 2; + } + + print_score(); +} From f6693474682500052255953a10253b13277679ed Mon Sep 17 00:00:00 2001 From: kbrannen Date: Thu, 20 Apr 2023 12:33:38 -0500 Subject: [PATCH 111/141] removed uneeded line --- 07_Basketball/perl/basketball.pl | 1 - 1 file changed, 1 deletion(-) diff --git a/07_Basketball/perl/basketball.pl b/07_Basketball/perl/basketball.pl index a6f98041..ac348309 100755 --- a/07_Basketball/perl/basketball.pl +++ b/07_Basketball/perl/basketball.pl @@ -50,7 +50,6 @@ while ($DoPlay) print "\n"; if ($ConTeam eq "dart") { - $Player = 0; get_your_shot(); dartmouth_play(); } From d300d2d851a72fce9470cd8386eec94e38d96a3b Mon Sep 17 00:00:00 2001 From: kbrannen Date: Fri, 21 Apr 2023 00:25:47 -0500 Subject: [PATCH 112/141] added 13-bounce for Perl --- 13_Bounce/perl/README.md | 3 ++ 13_Bounce/perl/bounce.pl | 91 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100755 13_Bounce/perl/bounce.pl diff --git a/13_Bounce/perl/README.md b/13_Bounce/perl/README.md index e69c8b81..6d213dba 100644 --- a/13_Bounce/perl/README.md +++ b/13_Bounce/perl/README.md @@ -1,3 +1,6 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) Conversion to [Perl](https://www.perl.org/) + +Added feature so that if "TIME" value is "0" then it will quit, +so you don't have to hit Control-C. Also added a little error checking of the input. diff --git a/13_Bounce/perl/bounce.pl b/13_Bounce/perl/bounce.pl new file mode 100755 index 00000000..023f6f04 --- /dev/null +++ b/13_Bounce/perl/bounce.pl @@ -0,0 +1,91 @@ +#!/usr/bin/perl + +# Bounce program in Perl +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +print "\n"; +print " " x 31,"BOUNCE\n"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n"; +print "\n\n\n"; + +print "THIS SIMULATION LETS YOU SPECIFY THE INITIAL VELOCITY\n"; +print "OF A BALL THROWN STRAIGHT UP, AND THE COEFFICIENT OF\n"; +print "ELASTICITY OF THE BALL. PLEASE USE A DECIMAL FRACTION\n"; +print "COEFFICIENCY (LESS THAN 1).\n\n"; +print "YOU ALSO SPECIFY THE TIME INCREMENT TO BE USED IN\n"; +print "'STROBING' THE BALL'S FLIGHT (TRY .1 INITIALLY).\n\n"; + +# deal with basic's tab() for line positioning +# line = line string we're starting with +# pos = position to start writing +# s = string to write +# returns the resultant string, which might not have been changed +sub line_tab +{ + my ($line, $pos, $s) = @_; + my $len = length($line); + # if curser is past position, do nothing + if ($len <= $pos) { $line .= " " x ($pos - $len) . $s; } + return $line; +} + +while (1) +{ + my @T; # time slice? + my $time_inc; # time increment, probably in fractions of seconds + my $velocity; # velocity in feet/sec + my $coeff_elas; # coeeficent of elasticity + my $L; # position on line + + # get input + print "TIME INCREMENT (SEC, 0=QUIT): "; chomp($time_inc = <>); + last if ($time_inc == 0); + print "VELOCITY (FPS): "; chomp($velocity = <>); + print "COEFFICIENT: "; chomp($coeff_elas = <>); + if ($coeff_elas >= 1.0 || $coeff_elas <= 0) + { + print "COEFFICIENT MUST BE > 0 AND < 1.0\n\n\n"; + next; + } + + print "\nFEET\n"; + my $S1 = int(70.0 / ($velocity / (16.0 * $time_inc))); + for my $i (1 .. $S1) + { + $T[$i] = $velocity * $coeff_elas ** ($i - 1) / 16.0; + } + + # draw graph + for (my $height=int(-16.0 * ($velocity / 32.0) ** 2.0 + $velocity ** 2.0 / 32.0 + .5) ; $height >= 0 ; $height -= .5) + { + #print "h=$height\n"; # kevin + if (int($height) == $height) { print sprintf("%2d", $height); } + else { print " "; } + $L = 0; + my $curr_line = ""; + for my $i (1 .. $S1) + { + my $time; + for ($time=0 ; $time <= $T[$i] ; $time += $time_inc) + { + $L += $time_inc; + next if (abs($height - (.5 * (-32) * $time ** 2.0 + $velocity * $coeff_elas ** ($i - 1) * $time)) > .25); + $curr_line = line_tab($curr_line, ($L / $time_inc), "0"); + } + $time = ($T[$i + 1] // 0) / 2; + last if (-16.0 * $time ** 2.0 + $velocity * $coeff_elas ** ($i - 1) * $time < $height); + } + print "$curr_line\n"; + } + + print " ."; + print "." x (int($L + 1) / $time_inc + 1), "\n"; + print " 0"; + my $cl = ""; + for my $i (1 .. int($L + .9995)) { $cl = line_tab($cl, int($i / $time_inc), $i); } + print "$cl\n"; + print " " x (int($L + 1) / (2 * $time_inc) - 2), "SECONDS\n\n"; +} From d24a5feb6b4e72f15016452844884c3355b48620 Mon Sep 17 00:00:00 2001 From: kbrannen Date: Fri, 21 Apr 2023 00:31:04 -0500 Subject: [PATCH 113/141] fixed a var name --- 13_Bounce/perl/bounce.pl | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/13_Bounce/perl/bounce.pl b/13_Bounce/perl/bounce.pl index 023f6f04..0d799514 100755 --- a/13_Bounce/perl/bounce.pl +++ b/13_Bounce/perl/bounce.pl @@ -38,7 +38,8 @@ while (1) my $time_inc; # time increment, probably in fractions of seconds my $velocity; # velocity in feet/sec my $coeff_elas; # coeeficent of elasticity - my $L; # position on line + my $line_pos; # position on line + my $S1 # duration in full seconds? # get input print "TIME INCREMENT (SEC, 0=QUIT): "; chomp($time_inc = <>); @@ -52,7 +53,7 @@ while (1) } print "\nFEET\n"; - my $S1 = int(70.0 / ($velocity / (16.0 * $time_inc))); + $S1 = int(70.0 / ($velocity / (16.0 * $time_inc))); for my $i (1 .. $S1) { $T[$i] = $velocity * $coeff_elas ** ($i - 1) / 16.0; @@ -64,28 +65,28 @@ while (1) #print "h=$height\n"; # kevin if (int($height) == $height) { print sprintf("%2d", $height); } else { print " "; } - $L = 0; + $line_pos = 0; my $curr_line = ""; for my $i (1 .. $S1) { my $time; for ($time=0 ; $time <= $T[$i] ; $time += $time_inc) { - $L += $time_inc; + $line_pos += $time_inc; next if (abs($height - (.5 * (-32) * $time ** 2.0 + $velocity * $coeff_elas ** ($i - 1) * $time)) > .25); - $curr_line = line_tab($curr_line, ($L / $time_inc), "0"); + $curr_line = line_tab($curr_line, ($line_pos / $time_inc), "0"); } - $time = ($T[$i + 1] // 0) / 2; + $time = ($T[$i + 1] // 0) / 2; # we can reach 1 past the end, use 0 if that happens last if (-16.0 * $time ** 2.0 + $velocity * $coeff_elas ** ($i - 1) * $time < $height); } print "$curr_line\n"; } print " ."; - print "." x (int($L + 1) / $time_inc + 1), "\n"; + print "." x (int($line_pos + 1) / $time_inc + 1), "\n"; print " 0"; - my $cl = ""; - for my $i (1 .. int($L + .9995)) { $cl = line_tab($cl, int($i / $time_inc), $i); } - print "$cl\n"; - print " " x (int($L + 1) / (2 * $time_inc) - 2), "SECONDS\n\n"; + my $curr_line = ""; + for my $i (1 .. int($line_pos + .9995)) { $curr_line = line_tab($curr_line, int($i / $time_inc), $i); } + print "$curr_line\n"; + print " " x (int($line_pos + 1) / (2 * $time_inc) - 2), "SECONDS\n\n"; } From 5ae3fe8774d845079591056ed71033162081e3a1 Mon Sep 17 00:00:00 2001 From: kbrannen Date: Fri, 21 Apr 2023 00:37:09 -0500 Subject: [PATCH 114/141] small fixes to make code look better --- 13_Bounce/perl/bounce.pl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/13_Bounce/perl/bounce.pl b/13_Bounce/perl/bounce.pl index 0d799514..4aeacf1f 100755 --- a/13_Bounce/perl/bounce.pl +++ b/13_Bounce/perl/bounce.pl @@ -62,7 +62,6 @@ while (1) # draw graph for (my $height=int(-16.0 * ($velocity / 32.0) ** 2.0 + $velocity ** 2.0 / 32.0 + .5) ; $height >= 0 ; $height -= .5) { - #print "h=$height\n"; # kevin if (int($height) == $height) { print sprintf("%2d", $height); } else { print " "; } $line_pos = 0; @@ -73,8 +72,10 @@ while (1) for ($time=0 ; $time <= $T[$i] ; $time += $time_inc) { $line_pos += $time_inc; - next if (abs($height - (.5 * (-32) * $time ** 2.0 + $velocity * $coeff_elas ** ($i - 1) * $time)) > .25); - $curr_line = line_tab($curr_line, ($line_pos / $time_inc), "0"); + if (abs($height - (.5 * (-32) * $time ** 2.0 + $velocity * $coeff_elas ** ($i - 1) * $time)) <= .25) + { + $curr_line = line_tab($curr_line, ($line_pos / $time_inc), "0"); + } } $time = ($T[$i + 1] // 0) / 2; # we can reach 1 past the end, use 0 if that happens last if (-16.0 * $time ** 2.0 + $velocity * $coeff_elas ** ($i - 1) * $time < $height); @@ -82,11 +83,15 @@ while (1) print "$curr_line\n"; } + # draw scale print " ."; print "." x (int($line_pos + 1) / $time_inc + 1), "\n"; print " 0"; my $curr_line = ""; - for my $i (1 .. int($line_pos + .9995)) { $curr_line = line_tab($curr_line, int($i / $time_inc), $i); } + for my $i (1 .. int($line_pos + .9995)) + { + $curr_line = line_tab($curr_line, int($i / $time_inc), $i); + } print "$curr_line\n"; print " " x (int($line_pos + 1) / (2 * $time_inc) - 2), "SECONDS\n\n"; } From b2de1b09e14bc16df3c6f36d896cc0555b75cfda Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Sat, 22 Apr 2023 07:56:35 +1000 Subject: [PATCH 115/141] CSHARP-77 Add HumnShotSelctor and some fixes --- 77_Salvo/csharp/Game.cs | 143 +++++++++++++++++++++++++++------------- 77_Salvo/salvo.bas | 2 - 2 files changed, 98 insertions(+), 47 deletions(-) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index 312796db..491cebdb 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -19,7 +19,6 @@ internal class Game _io.Write(Streams.Title); var hitTurnRecord = new int[13]; - var shots = new Position[8]; var temp = new Position[13]; var hitShipValue = new float[13]; @@ -30,6 +29,7 @@ internal class Game } var computerGrid = new Grid(_random); var humanGrid = new Grid(_io); + var humanShotSelector = new HumanShotSelector(humanGrid, computerGrid); var startResponse = _io.ReadString("DO YOU WANT TO START"); while (startResponse == "WHERE ARE YOUR SHIPS?") { @@ -47,11 +47,7 @@ L1950: if (startResponse != "YES") { goto L1990; } L1960: turnNumber++; L1970: _io.WriteLine(); L1980: _io.WriteLine($"TURN {turnNumber}"); -L1990: var maxShotCount = humanGrid.Ships.Sum(s => s.Shots); -L2090: for (var i = 1; i <= 7; i++) - { - shots[i] = temp[i] = 0; - } +L1990: var maxShotCount = humanShotSelector.GetShotCount(); L2220: _io.WriteLine($"YOU HAVE {maxShotCount} SHOTS."); if (maxShotCount == 0) { goto L2270; } L2230: if (maxShotCount > computerGrid.UntriedSquareCount) @@ -59,33 +55,13 @@ L2230: if (maxShotCount > computerGrid.UntriedSquareCount) _io.WriteLine("YOU HAVE MORE SHOTS THAN THERE ARE BLANK SQUARES."); L2250: goto L2890; } -L2290: for (var i = 1; i <= maxShotCount; i++) + foreach (var shot1 in humanShotSelector.GetShots(_io)) { - while (true) + if (computerGrid.IsHit(shot1, turnNumber, out var shipName)) { - var position = _io.ReadValidPosition(); -L2390: if (computerGrid[position]>10) - { - _io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {computerGrid[position]-10}"); - continue; - } - shots[i]= position; - break; + _io.WriteLine($"YOU HIT MY {shipName}."); } } -L2460: for (var W = 1; W <= maxShotCount; W++) - { - var hit = computerGrid[shots[W]] switch - { - 3 => "YOU HIT MY BATTLESHIP.", - 2 => "YOU HIT MY CRUISER.", - 1 => "YOU HIT MY DESTROYER.", - .5F => "YOU HIT MY DESTROYER.", - _ => null - }; - if (hit is not null) { _io.WriteLine(); } -L2510: computerGrid[shots[W]] = 10+turnNumber; - } L2620: if (startResponse == "YES") { goto L2670; } L2640: turnNumber++; L2650: _io.WriteLine(); @@ -100,7 +76,6 @@ L2880: if (maxShotCount != 0) { goto L2960; } L2890: _io.WriteLine("YOU HAVE WON."); L2900: return; - L2960: for (var i = 1; i <= 12; i++) { // if damaged ships @@ -250,6 +225,72 @@ L4230: goto L3380; } } +internal abstract class ShotSelector +{ + internal ShotSelector(Grid source, Grid target) + { + Source = source; + Target = target; + } + + protected Grid Source { get; } + protected Grid Target { get; } + + public int GetShotCount() => Source.Ships.Sum(s => s.Shots); +} + +internal abstract class ComputerShotSelector : ShotSelector +{ + private readonly bool _displayShots; + + internal ComputerShotSelector(Grid source, Grid target, bool displayShots) + : base(source, target) + { + _displayShots = displayShots; + } + + private void DisplayShots(IEnumerable shots, IReadWrite io) + { + if (_displayShots) + { + foreach (var shot in shots) + { + io.WriteLine(shot); + } + } + } +} + +internal class HumanShotSelector : ShotSelector +{ + public HumanShotSelector(Grid source, Grid target) + : base(source, target) + { + } + + public IEnumerable GetShots(IReadWrite io) + { + var shots = new Position[GetShotCount()]; + + for (var i = 0; i < shots.Length; i++) + { + while (true) + { + var position = io.ReadValidPosition(); + if (Target.WasTargetedAt(position, out var turnTargeted)) + { + io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {turnTargeted}"); + continue; + } + shots[i] = position; + break; + } + } + + return shots; + } +} + internal abstract class Ship { private readonly List _positions = new(); @@ -276,7 +317,7 @@ internal abstract class Ship internal abstract int Size { get; } internal abstract float Value { get; } internal IEnumerable Positions => _positions; - internal bool IsAfloat => _positions.Count > 0; + internal bool IsDestroyed => _positions.Count == 0; internal bool IsHit(Position position) => _positions.Remove(position); @@ -381,7 +422,7 @@ internal class Grid internal Grid(IReadWrite io) { - io.WriteLine("ENTER POSITION FOR..."); + io.WriteLine("ENTER COORDINATES FOR..."); _ships = new() { new Battleship(io), @@ -401,30 +442,23 @@ internal class Grid TryPositionShip(() => new Destroyer("A", random)) && TryPositionShip(() => new Destroyer("B", random))) { - break; + return; } _ships.Clear(); } - foreach (var ship in _ships) - { - foreach (var position in ship.Positions) - { - this[position] = ship.Value; - } - } - bool TryPositionShip(Func shipFactory) { + var shipGenerationAttempts = 0; while (true) { - var shipGenerationAttempts = 0; var ship = shipFactory.Invoke(); shipGenerationAttempts++; if (shipGenerationAttempts > 25) { return false; } - foreach (var previousShip in _ships) + if (_ships.Min(ship.DistanceTo) >= 3.59) { - if (ship.DistanceTo(previousShip) >= 3.59) { return true; } + _ships.Add(ship); + return true; } } } @@ -444,6 +478,22 @@ internal class Grid internal int UntriedSquareCount => 100 - _shots.Count; internal IEnumerable Ships => _ships.AsEnumerable(); + + internal bool WasTargetedAt(Position position, out int turnTargeted) + => _shots.TryGetValue(position, out turnTargeted); + + internal bool IsHit(Position position, int turnNumber, out string? shipName) + { + shipName = null; + _shots[position] = turnNumber; + + var ship = _ships.FirstOrDefault(s => s.IsHit(position)); + if (ship == null) { return false; } + + if (ship.IsDestroyed) { _ships.Remove(ship); } + + return true; + } } internal static class IOExtensions @@ -507,7 +557,10 @@ internal record struct Position(Coordinate X, Coordinate Y) } internal float DistanceTo(Position other) - => (float)Math.Sqrt((X - other.X) * (X - other.Y) + (Y - other.Y) * (Y - other.Y)); + { + var (deltaX, deltaY) = (X - other.X, Y - other.Y); + return (float)Math.Sqrt(deltaX * deltaX + deltaY * deltaY); + } internal Position BringIntoRange(IRandom random) => IsInRange ? this : new(X.BringIntoRange(random), Y.BringIntoRange(random)); diff --git a/77_Salvo/salvo.bas b/77_Salvo/salvo.bas index 71036081..6a85d715 100644 --- a/77_Salvo/salvo.bas +++ b/77_Salvo/salvo.bas @@ -43,8 +43,6 @@ 1410 FOR Z2= U8 TO U8+FNB(K) 1415 IF U8<2 THEN 1450 1420 FOR Z3=1 TO U8-1 -1421 D = SQR((F(Z3)-F(Z2))^2 + (G(Z3)-G(Z2))^2) -1422 PRINT "(";F(Z3);G(Z3);")","(";F(Z2);G(Z2);")",D 1430 IF SQR((F(Z3)-F(Z2))^2 + (G(Z3)-G(Z2))^2) < 3.59 THEN 1260 1440 NEXT Z3 1450 NEXT Z2 From 3e4fb4fe237bbea7232232551a4b36960c418f03 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Sat, 22 Apr 2023 08:09:50 +1000 Subject: [PATCH 116/141] Split classes to files --- 77_Salvo/csharp/Coordinate.cs | 45 ++ 77_Salvo/csharp/Extensions/IOExtensions.cs | 27 ++ .../csharp/Extensions/RandomExtensions.cs | 32 ++ 77_Salvo/csharp/Game.cs | 419 +----------------- 77_Salvo/csharp/Grid.cs | 89 ++++ 77_Salvo/csharp/Offset.cs | 25 ++ 77_Salvo/csharp/Position.cs | 52 +++ 77_Salvo/csharp/Program.cs | 3 +- 77_Salvo/csharp/Ships/Battleship.cs | 18 + 77_Salvo/csharp/Ships/Cruiser.cs | 18 + 77_Salvo/csharp/Ships/Destroyer.cs | 18 + 77_Salvo/csharp/Ships/Ship.cs | 38 ++ .../csharp/Targetting/ComputerShotSelector.cs | 23 + .../csharp/Targetting/HumanShotSelector.cs | 31 ++ 77_Salvo/csharp/Targetting/ShotSelector.cs | 15 + 15 files changed, 434 insertions(+), 419 deletions(-) create mode 100644 77_Salvo/csharp/Coordinate.cs create mode 100644 77_Salvo/csharp/Extensions/IOExtensions.cs create mode 100644 77_Salvo/csharp/Extensions/RandomExtensions.cs create mode 100644 77_Salvo/csharp/Grid.cs create mode 100644 77_Salvo/csharp/Offset.cs create mode 100644 77_Salvo/csharp/Position.cs create mode 100644 77_Salvo/csharp/Ships/Battleship.cs create mode 100644 77_Salvo/csharp/Ships/Cruiser.cs create mode 100644 77_Salvo/csharp/Ships/Destroyer.cs create mode 100644 77_Salvo/csharp/Ships/Ship.cs create mode 100644 77_Salvo/csharp/Targetting/ComputerShotSelector.cs create mode 100644 77_Salvo/csharp/Targetting/HumanShotSelector.cs create mode 100644 77_Salvo/csharp/Targetting/ShotSelector.cs diff --git a/77_Salvo/csharp/Coordinate.cs b/77_Salvo/csharp/Coordinate.cs new file mode 100644 index 00000000..45d149c1 --- /dev/null +++ b/77_Salvo/csharp/Coordinate.cs @@ -0,0 +1,45 @@ +namespace Salvo; + +internal record struct Coordinate(int Value) +{ + public const int MinValue = 0; + public const int MaxValue = 9; + + public static IEnumerable Range => Enumerable.Range(0, 10).Select(v => new Coordinate(v)); + + public bool IsInRange => Value is >= MinValue and <= MaxValue; + + public static Coordinate Create(float value) => new((int)value - 1); + + public static bool TryCreateValid(float value, out Coordinate coordinate) + { + coordinate = default; + if (value != (int)value) { return false; } + + var result = Create(value); + + if (result.IsInRange) + { + coordinate = result; + return true; + } + + return false; + } + + public Coordinate BringIntoRange(IRandom random) + => Value switch + { + < MinValue => new(MinValue + (int)random.NextFloat(2.5F)), + > MaxValue => new(MaxValue - (int)random.NextFloat(2.5F)), + _ => this + }; + + public static implicit operator Coordinate(float value) => new((int)value); + public static implicit operator int(Coordinate coordinate) => coordinate.Value; + + public static Coordinate operator +(Coordinate coordinate, int offset) => new(coordinate.Value + offset); + public static int operator -(Coordinate a, Coordinate b) => a.Value - b.Value; + + public override string ToString() => $" {Value + 1} "; +} diff --git a/77_Salvo/csharp/Extensions/IOExtensions.cs b/77_Salvo/csharp/Extensions/IOExtensions.cs new file mode 100644 index 00000000..b9eaa2c4 --- /dev/null +++ b/77_Salvo/csharp/Extensions/IOExtensions.cs @@ -0,0 +1,27 @@ +namespace Games.Common.IO; + +internal static class IOExtensions +{ + internal static Position ReadPosition(this IReadWrite io) => Position.Create(io.Read2Numbers("")); + + internal static Position ReadValidPosition(this IReadWrite io) + { + while (true) + { + if (Position.TryCreateValid(io.Read2Numbers(""), out var position)) + { + return position; + } + io.WriteLine("ILLEGAL, ENTER AGAIN."); + } + } + + internal static IEnumerable ReadPositions(this IReadWrite io, string shipName, int shipSize) + { + io.WriteLine(shipName); + for (var i = 0; i < shipSize; i++) + { + yield return io.ReadPosition(); + } + } +} diff --git a/77_Salvo/csharp/Extensions/RandomExtensions.cs b/77_Salvo/csharp/Extensions/RandomExtensions.cs new file mode 100644 index 00000000..e83ab265 --- /dev/null +++ b/77_Salvo/csharp/Extensions/RandomExtensions.cs @@ -0,0 +1,32 @@ +namespace Games.Common.Randomness; + +internal static class RandomExtensions +{ + internal static (Position, Offset) NextShipPosition(this IRandom random) + { + var startX = random.NextCoordinate(); + var startY = random.NextCoordinate(); + var deltaY = random.NextOffset(); + var deltaX = random.NextOffset(); + return (new(startX, startY), new(deltaX, deltaY)); + } + + private static Coordinate NextCoordinate(this IRandom random) + => random.Next(Coordinate.MinValue, Coordinate.MaxValue + 1); + + private static int NextOffset(this IRandom random) => random.Next(-1, 2); + + internal static (Position, Offset) GetRandomShipPositionInRange(this IRandom random, int shipSize) + { + while (true) + { + var (start, delta) = random.NextShipPosition(); + var shipSizeLessOne = shipSize - 1; + var end = start + delta * shipSizeLessOne; + if (delta != 0 && end.IsInRange) + { + return (start, delta); + } + } + } +} diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index 491cebdb..2afac1bd 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -1,5 +1,4 @@ -using System.Collections.Immutable; -using Games.Common.Randomness; +using Salvo.Targetting; namespace Salvo; @@ -224,419 +223,3 @@ L4210: ;// NoOp - NEXT S L4230: goto L3380; } } - -internal abstract class ShotSelector -{ - internal ShotSelector(Grid source, Grid target) - { - Source = source; - Target = target; - } - - protected Grid Source { get; } - protected Grid Target { get; } - - public int GetShotCount() => Source.Ships.Sum(s => s.Shots); -} - -internal abstract class ComputerShotSelector : ShotSelector -{ - private readonly bool _displayShots; - - internal ComputerShotSelector(Grid source, Grid target, bool displayShots) - : base(source, target) - { - _displayShots = displayShots; - } - - private void DisplayShots(IEnumerable shots, IReadWrite io) - { - if (_displayShots) - { - foreach (var shot in shots) - { - io.WriteLine(shot); - } - } - } -} - -internal class HumanShotSelector : ShotSelector -{ - public HumanShotSelector(Grid source, Grid target) - : base(source, target) - { - } - - public IEnumerable GetShots(IReadWrite io) - { - var shots = new Position[GetShotCount()]; - - for (var i = 0; i < shots.Length; i++) - { - while (true) - { - var position = io.ReadValidPosition(); - if (Target.WasTargetedAt(position, out var turnTargeted)) - { - io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {turnTargeted}"); - continue; - } - shots[i] = position; - break; - } - } - - return shots; - } -} - -internal abstract class Ship -{ - private readonly List _positions = new(); - - protected Ship(IReadWrite io, string? nameSuffix = null) - { - Name = GetType().Name + nameSuffix; - _positions = io.ReadPositions(Name, Size).ToList(); - } - - protected Ship(IRandom random, string? nameSuffix = null) - { - Name = GetType().Name + nameSuffix; - - var (start, delta) = random.GetRandomShipPositionInRange(Size); - for (var i = 0; i < Size; i++) - { - _positions.Add(start + delta * i); - } - } - - internal string Name { get; } - internal abstract int Shots { get; } - internal abstract int Size { get; } - internal abstract float Value { get; } - internal IEnumerable Positions => _positions; - internal bool IsDestroyed => _positions.Count == 0; - - internal bool IsHit(Position position) => _positions.Remove(position); - - internal float DistanceTo(Ship other) - => _positions.SelectMany(a => other._positions.Select(b => a.DistanceTo(b))).Min(); - - public override string ToString() - => string.Join(Environment.NewLine, _positions.Select(p => p.ToString()).Prepend(Name)); -} - -internal sealed class Battleship : Ship -{ - internal Battleship(IReadWrite io) - : base(io) - { - } - - internal Battleship(IRandom random) - : base(random) - { - } - - internal override int Shots => 3; - internal override int Size => 5; - internal override float Value => 3; -} - -internal sealed class Cruiser : Ship -{ - internal Cruiser(IReadWrite io) - : base(io) - { - } - - internal Cruiser(IRandom random) - : base(random) - { - } - - internal override int Shots => 2; - internal override int Size => 3; - internal override float Value => 2; -} - -internal sealed class Destroyer : Ship -{ - internal Destroyer(string nameIndex, IReadWrite io) - : base(io, $"<{nameIndex}>") - { - } - - internal Destroyer(string nameIndex, IRandom random) - : base(random, $"<{nameIndex}>") - { - } - - internal override int Shots => 1; - internal override int Size => 2; - internal override float Value => Name.EndsWith("") ? 1 : 0.5F; -} - -internal static class RandomExtensions -{ - internal static (Position, Offset) NextShipPosition(this IRandom random) - { - var startX = random.NextCoordinate(); - var startY = random.NextCoordinate(); - var deltaY = random.NextOffset(); - var deltaX = random.NextOffset(); - return (new(startX, startY), new(deltaX, deltaY)); - } - - private static Coordinate NextCoordinate(this IRandom random) - => random.Next(Coordinate.MinValue, Coordinate.MaxValue + 1); - - private static int NextOffset(this IRandom random) => random.Next(-1, 2); - - internal static (Position, Offset) GetRandomShipPositionInRange(this IRandom random, int shipSize) - { - while (true) - { - var (start, delta) = random.NextShipPosition(); - var shipSizeLessOne = shipSize - 1; - var end = start + delta * shipSizeLessOne; - if (delta != 0 && end.IsInRange) - { - return (start, delta); - } - } - } -} - -internal class Grid -{ - private readonly List _ships; - private readonly Dictionary _shots = new(); - - internal Grid() - { - _ships = new(); - } - - internal Grid(IReadWrite io) - { - io.WriteLine("ENTER COORDINATES FOR..."); - _ships = new() - { - new Battleship(io), - new Cruiser(io), - new Destroyer("A", io), - new Destroyer("B", io) - }; - } - - internal Grid(IRandom random) - { - _ships = new(); - while (true) - { - _ships.Add(new Battleship(random)); - if (TryPositionShip(() => new Cruiser(random)) && - TryPositionShip(() => new Destroyer("A", random)) && - TryPositionShip(() => new Destroyer("B", random))) - { - return; - } - _ships.Clear(); - } - - bool TryPositionShip(Func shipFactory) - { - var shipGenerationAttempts = 0; - while (true) - { - var ship = shipFactory.Invoke(); - shipGenerationAttempts++; - if (shipGenerationAttempts > 25) { return false; } - if (_ships.Min(ship.DistanceTo) >= 3.59) - { - _ships.Add(ship); - return true; - } - } - } - } - - public float this[Position position] - { - get => _shots.TryGetValue(position, out var value) - ? value + 10 - : _ships.FirstOrDefault(s => s.Positions.Contains(position))?.Value ?? 0; - set - { - _ = _ships.FirstOrDefault(s => s.IsHit(position)); - _shots[position] = (int)value - 10; - } - } - - internal int UntriedSquareCount => 100 - _shots.Count; - internal IEnumerable Ships => _ships.AsEnumerable(); - - internal bool WasTargetedAt(Position position, out int turnTargeted) - => _shots.TryGetValue(position, out turnTargeted); - - internal bool IsHit(Position position, int turnNumber, out string? shipName) - { - shipName = null; - _shots[position] = turnNumber; - - var ship = _ships.FirstOrDefault(s => s.IsHit(position)); - if (ship == null) { return false; } - - if (ship.IsDestroyed) { _ships.Remove(ship); } - - return true; - } -} - -internal static class IOExtensions -{ - internal static Position ReadPosition(this IReadWrite io) => Position.Create(io.Read2Numbers("")); - - internal static Position ReadValidPosition(this IReadWrite io) - { - while (true) - { - if (Position.TryCreateValid(io.Read2Numbers(""), out var position)) - { - return position; - } - io.WriteLine("ILLEGAL, ENTER AGAIN."); - } - } - - internal static IEnumerable ReadPositions(this IReadWrite io, string shipName, int shipSize) - { - io.WriteLine(shipName); - for (var i = 0; i < shipSize; i++) - { - yield return io.ReadPosition(); - } - } -} - -internal record struct Position(Coordinate X, Coordinate Y) -{ - public bool IsInRange => X.IsInRange && Y.IsInRange; - public bool IsOnDiagonal => X == Y; - - public static Position Create((float X, float Y) coordinates) => new(coordinates.X, coordinates.Y); - - public static bool TryCreateValid((float X, float Y) coordinates, out Position position) - { - if (Coordinate.TryCreateValid(coordinates.X, out var x) && Coordinate.TryCreateValid(coordinates.Y, out var y)) - { - position = new(x, y); - return true; - } - - position = default; - return false; - } - - public static IEnumerable All - => Coordinate.Range.SelectMany(x => Coordinate.Range.Select(y => new Position(x, y))); - - public IEnumerable Neighbours - { - get - { - foreach (var offset in Offset.Units) - { - var neighbour = this + offset; - if (neighbour.IsInRange) { yield return neighbour; } - } - } - } - - internal float DistanceTo(Position other) - { - var (deltaX, deltaY) = (X - other.X, Y - other.Y); - return (float)Math.Sqrt(deltaX * deltaX + deltaY * deltaY); - } - - internal Position BringIntoRange(IRandom random) - => IsInRange ? this : new(X.BringIntoRange(random), Y.BringIntoRange(random)); - - public static Position operator +(Position position, Offset offset) - => new(position.X + offset.X, position.Y + offset.Y); - - public static implicit operator Position(int value) => new(value, value); - - public override string ToString() => $"{X}{Y}"; -} - -internal record struct Coordinate(int Value) -{ - public const int MinValue = 0; - public const int MaxValue = 9; - - public static IEnumerable Range => Enumerable.Range(0, 10).Select(v => new Coordinate(v)); - - public bool IsInRange => Value is >= MinValue and <= MaxValue; - - public static Coordinate Create(float value) => new((int)value - 1); - - public static bool TryCreateValid(float value, out Coordinate coordinate) - { - coordinate = default; - if (value != (int)value) { return false; } - - var result = Create(value); - - if (result.IsInRange) - { - coordinate = result; - return true; - } - - return false; - } - - public Coordinate BringIntoRange(IRandom random) - => Value switch - { - < MinValue => new(MinValue + (int)random.NextFloat(2.5F)), - > MaxValue => new(MaxValue - (int)random.NextFloat(2.5F)), - _ => this - }; - - public static implicit operator Coordinate(float value) => new((int)value); - public static implicit operator int(Coordinate coordinate) => coordinate.Value; - - public static Coordinate operator +(Coordinate coordinate, int offset) => new(coordinate.Value + offset); - public static int operator -(Coordinate a, Coordinate b) => a.Value - b.Value; - - public override string ToString() => $" {Value + 1} "; -} - -internal record struct Offset(int X, int Y) -{ - public static readonly Offset Zero = 0; - - public static Offset operator *(Offset offset, int scale) => new(offset.X * scale, offset.Y * scale); - - public static implicit operator Offset(int value) => new(value, value); - - public static IEnumerable Units - { - get - { - for (int x = -1; x <= 1; x++) - { - for (int y = -1; y <= 1; y++) - { - var offset = new Offset(x, y); - if (offset != Zero) { yield return offset; } - } - } - } - } -} diff --git a/77_Salvo/csharp/Grid.cs b/77_Salvo/csharp/Grid.cs new file mode 100644 index 00000000..c3d04dff --- /dev/null +++ b/77_Salvo/csharp/Grid.cs @@ -0,0 +1,89 @@ +using System.Collections.Immutable; + +namespace Salvo; + +internal class Grid +{ + private readonly List _ships; + private readonly Dictionary _shots = new(); + + internal Grid() + { + _ships = new(); + } + + internal Grid(IReadWrite io) + { + io.WriteLine("ENTER COORDINATES FOR..."); + _ships = new() + { + new Battleship(io), + new Cruiser(io), + new Destroyer("A", io), + new Destroyer("B", io) + }; + } + + internal Grid(IRandom random) + { + _ships = new(); + while (true) + { + _ships.Add(new Battleship(random)); + if (TryPositionShip(() => new Cruiser(random)) && + TryPositionShip(() => new Destroyer("A", random)) && + TryPositionShip(() => new Destroyer("B", random))) + { + return; + } + _ships.Clear(); + } + + bool TryPositionShip(Func shipFactory) + { + var shipGenerationAttempts = 0; + while (true) + { + var ship = shipFactory.Invoke(); + shipGenerationAttempts++; + if (shipGenerationAttempts > 25) { return false; } + if (_ships.Min(ship.DistanceTo) >= 3.59) + { + _ships.Add(ship); + return true; + } + } + } + } + + public float this[Position position] + { + get => _shots.TryGetValue(position, out var value) + ? value + 10 + : _ships.FirstOrDefault(s => s.Positions.Contains(position))?.Value ?? 0; + set + { + _ = _ships.FirstOrDefault(s => s.IsHit(position)); + _shots[position] = (int)value - 10; + } + } + + internal int UntriedSquareCount => 100 - _shots.Count; + internal IEnumerable Ships => _ships.AsEnumerable(); + + internal bool WasTargetedAt(Position position, out int turnTargeted) + => _shots.TryGetValue(position, out turnTargeted); + + internal bool IsHit(Position position, int turnNumber, out string? shipName) + { + shipName = null; + _shots[position] = turnNumber; + + var ship = _ships.FirstOrDefault(s => s.IsHit(position)); + if (ship == null) { return false; } + + if (ship.IsDestroyed) { _ships.Remove(ship); } + + return true; + } +} diff --git a/77_Salvo/csharp/Offset.cs b/77_Salvo/csharp/Offset.cs new file mode 100644 index 00000000..3c873f10 --- /dev/null +++ b/77_Salvo/csharp/Offset.cs @@ -0,0 +1,25 @@ +namespace Salvo; + +internal record struct Offset(int X, int Y) +{ + public static readonly Offset Zero = 0; + + public static Offset operator *(Offset offset, int scale) => new(offset.X * scale, offset.Y * scale); + + public static implicit operator Offset(int value) => new(value, value); + + public static IEnumerable Units + { + get + { + for (int x = -1; x <= 1; x++) + { + for (int y = -1; y <= 1; y++) + { + var offset = new Offset(x, y); + if (offset != Zero) { yield return offset; } + } + } + } + } +} diff --git a/77_Salvo/csharp/Position.cs b/77_Salvo/csharp/Position.cs new file mode 100644 index 00000000..3bafe217 --- /dev/null +++ b/77_Salvo/csharp/Position.cs @@ -0,0 +1,52 @@ +namespace Salvo; + +internal record struct Position(Coordinate X, Coordinate Y) +{ + public bool IsInRange => X.IsInRange && Y.IsInRange; + public bool IsOnDiagonal => X == Y; + + public static Position Create((float X, float Y) coordinates) => new(coordinates.X, coordinates.Y); + + public static bool TryCreateValid((float X, float Y) coordinates, out Position position) + { + if (Coordinate.TryCreateValid(coordinates.X, out var x) && Coordinate.TryCreateValid(coordinates.Y, out var y)) + { + position = new(x, y); + return true; + } + + position = default; + return false; + } + + public static IEnumerable All + => Coordinate.Range.SelectMany(x => Coordinate.Range.Select(y => new Position(x, y))); + + public IEnumerable Neighbours + { + get + { + foreach (var offset in Offset.Units) + { + var neighbour = this + offset; + if (neighbour.IsInRange) { yield return neighbour; } + } + } + } + + internal float DistanceTo(Position other) + { + var (deltaX, deltaY) = (X - other.X, Y - other.Y); + return (float)Math.Sqrt(deltaX * deltaX + deltaY * deltaY); + } + + internal Position BringIntoRange(IRandom random) + => IsInRange ? this : new(X.BringIntoRange(random), Y.BringIntoRange(random)); + + public static Position operator +(Position position, Offset offset) + => new(position.X + offset.X, position.Y + offset.Y); + + public static implicit operator Position(int value) => new(value, value); + + public override string ToString() => $"{X}{Y}"; +} diff --git a/77_Salvo/csharp/Program.cs b/77_Salvo/csharp/Program.cs index b9d6a84c..6878fd38 100644 --- a/77_Salvo/csharp/Program.cs +++ b/77_Salvo/csharp/Program.cs @@ -1,7 +1,8 @@ global using System; global using Games.Common.IO; global using Games.Common.Randomness; +global using Salvo; +global using Salvo.Ships; global using static Salvo.Resources.Resource; -using Salvo; new Game(new ConsoleIO(), new RandomNumberGenerator()).Play(); diff --git a/77_Salvo/csharp/Ships/Battleship.cs b/77_Salvo/csharp/Ships/Battleship.cs new file mode 100644 index 00000000..1e57e59d --- /dev/null +++ b/77_Salvo/csharp/Ships/Battleship.cs @@ -0,0 +1,18 @@ +namespace Salvo.Ships; + +internal sealed class Battleship : Ship +{ + internal Battleship(IReadWrite io) + : base(io) + { + } + + internal Battleship(IRandom random) + : base(random) + { + } + + internal override int Shots => 3; + internal override int Size => 5; + internal override float Value => 3; +} diff --git a/77_Salvo/csharp/Ships/Cruiser.cs b/77_Salvo/csharp/Ships/Cruiser.cs new file mode 100644 index 00000000..33f30e38 --- /dev/null +++ b/77_Salvo/csharp/Ships/Cruiser.cs @@ -0,0 +1,18 @@ +namespace Salvo.Ships; + +internal sealed class Cruiser : Ship +{ + internal Cruiser(IReadWrite io) + : base(io) + { + } + + internal Cruiser(IRandom random) + : base(random) + { + } + + internal override int Shots => 2; + internal override int Size => 3; + internal override float Value => 2; +} diff --git a/77_Salvo/csharp/Ships/Destroyer.cs b/77_Salvo/csharp/Ships/Destroyer.cs new file mode 100644 index 00000000..0e2a023b --- /dev/null +++ b/77_Salvo/csharp/Ships/Destroyer.cs @@ -0,0 +1,18 @@ +namespace Salvo.Ships; + +internal sealed class Destroyer : Ship +{ + internal Destroyer(string nameIndex, IReadWrite io) + : base(io, $"<{nameIndex}>") + { + } + + internal Destroyer(string nameIndex, IRandom random) + : base(random, $"<{nameIndex}>") + { + } + + internal override int Shots => 1; + internal override int Size => 2; + internal override float Value => Name.EndsWith("") ? 1 : 0.5F; +} diff --git a/77_Salvo/csharp/Ships/Ship.cs b/77_Salvo/csharp/Ships/Ship.cs new file mode 100644 index 00000000..58f311c2 --- /dev/null +++ b/77_Salvo/csharp/Ships/Ship.cs @@ -0,0 +1,38 @@ +namespace Salvo.Ships; + +internal abstract class Ship +{ + private readonly List _positions = new(); + + protected Ship(IReadWrite io, string? nameSuffix = null) + { + Name = GetType().Name + nameSuffix; + _positions = io.ReadPositions(Name, Size).ToList(); + } + + protected Ship(IRandom random, string? nameSuffix = null) + { + Name = GetType().Name + nameSuffix; + + var (start, delta) = random.GetRandomShipPositionInRange(Size); + for (var i = 0; i < Size; i++) + { + _positions.Add(start + delta * i); + } + } + + internal string Name { get; } + internal abstract int Shots { get; } + internal abstract int Size { get; } + internal abstract float Value { get; } + internal IEnumerable Positions => _positions; + internal bool IsDestroyed => _positions.Count == 0; + + internal bool IsHit(Position position) => _positions.Remove(position); + + internal float DistanceTo(Ship other) + => _positions.SelectMany(a => other._positions.Select(b => a.DistanceTo(b))).Min(); + + public override string ToString() + => string.Join(Environment.NewLine, _positions.Select(p => p.ToString()).Prepend(Name)); +} diff --git a/77_Salvo/csharp/Targetting/ComputerShotSelector.cs b/77_Salvo/csharp/Targetting/ComputerShotSelector.cs new file mode 100644 index 00000000..1cf2a98a --- /dev/null +++ b/77_Salvo/csharp/Targetting/ComputerShotSelector.cs @@ -0,0 +1,23 @@ +namespace Salvo.Targetting; + +internal abstract class ComputerShotSelector : ShotSelector +{ + private readonly bool _displayShots; + + internal ComputerShotSelector(Grid source, Grid target, bool displayShots) + : base(source, target) + { + _displayShots = displayShots; + } + + private void DisplayShots(IEnumerable shots, IReadWrite io) + { + if (_displayShots) + { + foreach (var shot in shots) + { + io.WriteLine(shot); + } + } + } +} diff --git a/77_Salvo/csharp/Targetting/HumanShotSelector.cs b/77_Salvo/csharp/Targetting/HumanShotSelector.cs new file mode 100644 index 00000000..6a50330a --- /dev/null +++ b/77_Salvo/csharp/Targetting/HumanShotSelector.cs @@ -0,0 +1,31 @@ +namespace Salvo.Targetting; + +internal class HumanShotSelector : ShotSelector +{ + public HumanShotSelector(Grid source, Grid target) + : base(source, target) + { + } + + public IEnumerable GetShots(IReadWrite io) + { + var shots = new Position[GetShotCount()]; + + for (var i = 0; i < shots.Length; i++) + { + while (true) + { + var position = io.ReadValidPosition(); + if (Target.WasTargetedAt(position, out var turnTargeted)) + { + io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {turnTargeted}"); + continue; + } + shots[i] = position; + break; + } + } + + return shots; + } +} diff --git a/77_Salvo/csharp/Targetting/ShotSelector.cs b/77_Salvo/csharp/Targetting/ShotSelector.cs new file mode 100644 index 00000000..fae30f31 --- /dev/null +++ b/77_Salvo/csharp/Targetting/ShotSelector.cs @@ -0,0 +1,15 @@ +namespace Salvo.Targetting; + +internal abstract class ShotSelector +{ + internal ShotSelector(Grid source, Grid target) + { + Source = source; + Target = target; + } + + protected Grid Source { get; } + protected Grid Target { get; } + + public int GetShotCount() => Source.Ships.Sum(s => s.Shots); +} From d21b41d3a724cca452d65e0c99a168f5c931b6b2 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Sat, 22 Apr 2023 18:33:03 +1000 Subject: [PATCH 117/141] Add string resources --- 77_Salvo/csharp/Extensions/IOExtensions.cs | 2 +- 77_Salvo/csharp/Game.cs | 54 +++++++++---------- 77_Salvo/csharp/Grid.cs | 6 ++- 77_Salvo/csharp/Resources/Coordinates.txt | 1 + .../Resources/IHaveMoreShotsThanSquares.txt | 1 + 77_Salvo/csharp/Resources/IHaveShots.txt | 1 + 77_Salvo/csharp/Resources/IHit.txt | 1 + 77_Salvo/csharp/Resources/IWon.txt | 1 + 77_Salvo/csharp/Resources/Illegal.txt | 1 + 77_Salvo/csharp/Resources/Resource.cs | 21 +++++++- 77_Salvo/csharp/Resources/SeeShots.txt | 1 + 77_Salvo/csharp/Resources/ShotBefore.txt | 1 + 77_Salvo/csharp/Resources/Start.txt | 1 + 77_Salvo/csharp/Resources/Turn.txt | 2 + .../csharp/Resources/WhereAreYourShips.txt | 1 + .../Resources/YouHaveMoreShotsThanSquares.txt | 1 + 77_Salvo/csharp/Resources/YouHaveShots.txt | 1 + 77_Salvo/csharp/Resources/YouHit.txt | 1 + 77_Salvo/csharp/Resources/YouWon.txt | 1 + 19 files changed, 67 insertions(+), 32 deletions(-) create mode 100644 77_Salvo/csharp/Resources/Coordinates.txt create mode 100644 77_Salvo/csharp/Resources/IHaveMoreShotsThanSquares.txt create mode 100644 77_Salvo/csharp/Resources/IHaveShots.txt create mode 100644 77_Salvo/csharp/Resources/IHit.txt create mode 100644 77_Salvo/csharp/Resources/IWon.txt create mode 100644 77_Salvo/csharp/Resources/Illegal.txt create mode 100644 77_Salvo/csharp/Resources/SeeShots.txt create mode 100644 77_Salvo/csharp/Resources/ShotBefore.txt create mode 100644 77_Salvo/csharp/Resources/Start.txt create mode 100644 77_Salvo/csharp/Resources/Turn.txt create mode 100644 77_Salvo/csharp/Resources/WhereAreYourShips.txt create mode 100644 77_Salvo/csharp/Resources/YouHaveMoreShotsThanSquares.txt create mode 100644 77_Salvo/csharp/Resources/YouHaveShots.txt create mode 100644 77_Salvo/csharp/Resources/YouHit.txt create mode 100644 77_Salvo/csharp/Resources/YouWon.txt diff --git a/77_Salvo/csharp/Extensions/IOExtensions.cs b/77_Salvo/csharp/Extensions/IOExtensions.cs index b9eaa2c4..6d021deb 100644 --- a/77_Salvo/csharp/Extensions/IOExtensions.cs +++ b/77_Salvo/csharp/Extensions/IOExtensions.cs @@ -12,7 +12,7 @@ internal static class IOExtensions { return position; } - io.WriteLine("ILLEGAL, ENTER AGAIN."); + io.Write(Streams.Illegal); } } diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index 2afac1bd..2f3ebe8e 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -29,50 +29,48 @@ internal class Game var computerGrid = new Grid(_random); var humanGrid = new Grid(_io); var humanShotSelector = new HumanShotSelector(humanGrid, computerGrid); - var startResponse = _io.ReadString("DO YOU WANT TO START"); - while (startResponse == "WHERE ARE YOUR SHIPS?") + var startResponse = _io.ReadString(Prompts.Start); + while (startResponse == Strings.WhereAreYourShips) { foreach (var ship in computerGrid.Ships) { _io.WriteLine(ship); } - startResponse = _io.ReadString("DO YOU WANT TO START"); + startResponse = _io.ReadString(Prompts.Start); } L1890: var turnNumber=0; -L1900: var seeShotsResponse = _io.ReadString("DO YOU WANT TO SEE MY SHOTS"); +L1900: var seeShotsResponse = _io.ReadString(Prompts.SeeShots); L1920: _io.WriteLine(); L1930: if (startResponse != "YES") { goto L2620; } L1950: if (startResponse != "YES") { goto L1990; } L1960: turnNumber++; -L1970: _io.WriteLine(); -L1980: _io.WriteLine($"TURN {turnNumber}"); -L1990: var maxShotCount = humanShotSelector.GetShotCount(); -L2220: _io.WriteLine($"YOU HAVE {maxShotCount} SHOTS."); - if (maxShotCount == 0) { goto L2270; } -L2230: if (maxShotCount > computerGrid.UntriedSquareCount) +L1980: _io.Write(Strings.Turn(turnNumber)); +L1990: var numberOfShots = humanShotSelector.GetShotCount(); +L2220: _io.Write(Strings.YouHaveShots(numberOfShots)); + if (numberOfShots == 0) { goto L2270; } +L2230: if (numberOfShots > computerGrid.UntriedSquareCount) { - _io.WriteLine("YOU HAVE MORE SHOTS THAN THERE ARE BLANK SQUARES."); + _io.WriteLine(Streams.YouHaveMoreShotsThanSquares); L2250: goto L2890; } foreach (var shot1 in humanShotSelector.GetShots(_io)) { if (computerGrid.IsHit(shot1, turnNumber, out var shipName)) { - _io.WriteLine($"YOU HIT MY {shipName}."); + _io.Write(Strings.YouHit(shipName)); } } L2620: if (startResponse == "YES") { goto L2670; } L2640: turnNumber++; -L2650: _io.WriteLine(); -L2660: _io.WriteLine($"TURN {turnNumber}"); -L2670: maxShotCount = computerGrid.Ships.Sum(s => s.Shots); -L2840: _io.WriteLine($"I HAVE {maxShotCount} SHOTS."); -L2850: if (humanGrid.UntriedSquareCount > maxShotCount) { goto L2880; } -L2860: _io.WriteLine("I HAVE MORE SHOTS THAN BLANK SQUARES."); -L2270: _io.WriteLine("I HAVE WON."); +L2660: _io.Write(Strings.Turn(turnNumber)); +L2670: numberOfShots = computerGrid.Ships.Sum(s => s.Shots); +L2840: _io.Write(Strings.IHaveShots(numberOfShots)); +L2850: if (humanGrid.UntriedSquareCount > numberOfShots) { goto L2880; } +L2860: _io.Write(Streams.IHaveMoreShotsThanSquares); +L2270: _io.Write(Streams.IWon); return; -L2880: if (maxShotCount != 0) { goto L2960; } -L2890: _io.WriteLine("YOU HAVE WON."); +L2880: if (numberOfShots != 0) { goto L2960; } +L2890: _io.Write(Streams.YouWon); L2900: return; L2960: for (var i = 1; i <= 12; i++) @@ -91,7 +89,7 @@ L3070: shot = shot.BringIntoRange(_random); L3170: goto L3270; // record shot L3180: temp[shotCount]=shot; -L3200: if (shotCount==maxShotCount) { goto L3380; } +L3200: if (shotCount==numberOfShots) { goto L3380; } L3210: if (strategyNumber==6) { goto L3030; } L3240: //DATA 1,1,-1,1,1,-3,1,1,0,2,-1,1 var data = new Offset[] { new(1,1),new(-1,1),new(1,-3),new(1,1),new(0,2),new(-1,1) }; @@ -111,11 +109,11 @@ L3360: shotCount++; L3370: goto L3180; // display shots L3380: if (seeShotsResponse != "YES") { goto L3420; } -L3390: for (var i = 1; i <= maxShotCount; i++) +L3390: for (var i = 1; i <= numberOfShots; i++) { L3400: _io.WriteLine(temp[i]); } -L3420: for (var i = 1; i <= maxShotCount; i++) +L3420: for (var i = 1; i <= numberOfShots; i++) { L3430: if (humanGrid[temp[i]] == 3) { @@ -197,22 +195,22 @@ L3860: for (var i = 1; i <= 12; i++) } } } -L4030: for (var i = 1; i <= maxShotCount; i++) +L4030: for (var i = 1; i <= numberOfShots; i++) { L4040: temp[i]=i; } foreach (var position in Position.All) { L4090: var Q9=1; -L4100: for (var i = 1; i <= maxShotCount; i++) +L4100: for (var i = 1; i <= numberOfShots; i++) { L4110: if (tempGrid[temp[i]]>=tempGrid[temp[Q9]]) { continue; } L4120: Q9=i; } -L4131: if (position.X>maxShotCount) { goto L4140; } +L4131: if (position.X>numberOfShots) { goto L4140; } L4132: if (position.IsOnDiagonal) { goto L4210; } L4140: if (tempGrid[position] _shots.TryGetValue(position, out turnTargeted); - internal bool IsHit(Position position, int turnNumber, out string? shipName) + internal bool IsHit(Position position, int turnNumber, [NotNullWhen(true)] out string? shipName) { shipName = null; _shots[position] = turnNumber; var ship = _ships.FirstOrDefault(s => s.IsHit(position)); if (ship == null) { return false; } + shipName = ship.Name; if (ship.IsDestroyed) { _ships.Remove(ship); } diff --git a/77_Salvo/csharp/Resources/Coordinates.txt b/77_Salvo/csharp/Resources/Coordinates.txt new file mode 100644 index 00000000..387d7a6b --- /dev/null +++ b/77_Salvo/csharp/Resources/Coordinates.txt @@ -0,0 +1 @@ +Enter coordinates for... diff --git a/77_Salvo/csharp/Resources/IHaveMoreShotsThanSquares.txt b/77_Salvo/csharp/Resources/IHaveMoreShotsThanSquares.txt new file mode 100644 index 00000000..a6f37110 --- /dev/null +++ b/77_Salvo/csharp/Resources/IHaveMoreShotsThanSquares.txt @@ -0,0 +1 @@ +I have more shots than blank squares. diff --git a/77_Salvo/csharp/Resources/IHaveShots.txt b/77_Salvo/csharp/Resources/IHaveShots.txt new file mode 100644 index 00000000..9157e1a7 --- /dev/null +++ b/77_Salvo/csharp/Resources/IHaveShots.txt @@ -0,0 +1 @@ +I have {0} shots. diff --git a/77_Salvo/csharp/Resources/IHit.txt b/77_Salvo/csharp/Resources/IHit.txt new file mode 100644 index 00000000..a3f3dccf --- /dev/null +++ b/77_Salvo/csharp/Resources/IHit.txt @@ -0,0 +1 @@ +I hot your {0} diff --git a/77_Salvo/csharp/Resources/IWon.txt b/77_Salvo/csharp/Resources/IWon.txt new file mode 100644 index 00000000..b3f50634 --- /dev/null +++ b/77_Salvo/csharp/Resources/IWon.txt @@ -0,0 +1 @@ +I have won. diff --git a/77_Salvo/csharp/Resources/Illegal.txt b/77_Salvo/csharp/Resources/Illegal.txt new file mode 100644 index 00000000..6a71787a --- /dev/null +++ b/77_Salvo/csharp/Resources/Illegal.txt @@ -0,0 +1 @@ +Illegal, enter again. diff --git a/77_Salvo/csharp/Resources/Resource.cs b/77_Salvo/csharp/Resources/Resource.cs index 0cc55034..c3a0d8ca 100644 --- a/77_Salvo/csharp/Resources/Resource.cs +++ b/77_Salvo/csharp/Resources/Resource.cs @@ -8,13 +8,32 @@ internal static class Resource internal static class Streams { public static Stream Title => GetStream(); + public static Stream YouHaveMoreShotsThanSquares => GetStream(); + public static Stream YouWon => GetStream(); + public static Stream IHaveMoreShotsThanSquares => GetStream(); + public static Stream IWon => GetStream(); + public static Stream Illegal => GetStream(); + } + + internal static class Strings + { + public static string WhereAreYourShips => GetString(); + public static string YouHaveShots(int number) => Format(number); + public static string IHaveShots(int number) => Format(number); + public static string YouHit(string shipName) => Format(shipName); + public static string ShotBefore(int turnNumber) => Format(turnNumber); + public static string Turn(int number) => Format(number); } internal static class Prompts { + public static string Coordinates => GetString(); + public static string Start => GetString(); + public static string SeeShots => GetString(); } - private static string GetPrompt([CallerMemberName] string? name = null) => GetString($"{name}Prompt"); + private static string Format(T value, [CallerMemberName] string? name = null) + => string.Format(GetString(name), value); private static string GetString([CallerMemberName] string? name = null) { diff --git a/77_Salvo/csharp/Resources/SeeShots.txt b/77_Salvo/csharp/Resources/SeeShots.txt new file mode 100644 index 00000000..7f608b0b --- /dev/null +++ b/77_Salvo/csharp/Resources/SeeShots.txt @@ -0,0 +1 @@ +Do you want to see my shots \ No newline at end of file diff --git a/77_Salvo/csharp/Resources/ShotBefore.txt b/77_Salvo/csharp/Resources/ShotBefore.txt new file mode 100644 index 00000000..18e90fcb --- /dev/null +++ b/77_Salvo/csharp/Resources/ShotBefore.txt @@ -0,0 +1 @@ +You shot there before on turn {0} diff --git a/77_Salvo/csharp/Resources/Start.txt b/77_Salvo/csharp/Resources/Start.txt new file mode 100644 index 00000000..283a0fbb --- /dev/null +++ b/77_Salvo/csharp/Resources/Start.txt @@ -0,0 +1 @@ +Do you want to start \ No newline at end of file diff --git a/77_Salvo/csharp/Resources/Turn.txt b/77_Salvo/csharp/Resources/Turn.txt new file mode 100644 index 00000000..14bf316e --- /dev/null +++ b/77_Salvo/csharp/Resources/Turn.txt @@ -0,0 +1,2 @@ + +Turn {0} diff --git a/77_Salvo/csharp/Resources/WhereAreYourShips.txt b/77_Salvo/csharp/Resources/WhereAreYourShips.txt new file mode 100644 index 00000000..797c1144 --- /dev/null +++ b/77_Salvo/csharp/Resources/WhereAreYourShips.txt @@ -0,0 +1 @@ +Where are your ships? diff --git a/77_Salvo/csharp/Resources/YouHaveMoreShotsThanSquares.txt b/77_Salvo/csharp/Resources/YouHaveMoreShotsThanSquares.txt new file mode 100644 index 00000000..37c06812 --- /dev/null +++ b/77_Salvo/csharp/Resources/YouHaveMoreShotsThanSquares.txt @@ -0,0 +1 @@ +You have more shots that there are blank squares. diff --git a/77_Salvo/csharp/Resources/YouHaveShots.txt b/77_Salvo/csharp/Resources/YouHaveShots.txt new file mode 100644 index 00000000..43abeda8 --- /dev/null +++ b/77_Salvo/csharp/Resources/YouHaveShots.txt @@ -0,0 +1 @@ +You have {0} shots. diff --git a/77_Salvo/csharp/Resources/YouHit.txt b/77_Salvo/csharp/Resources/YouHit.txt new file mode 100644 index 00000000..b5b5d761 --- /dev/null +++ b/77_Salvo/csharp/Resources/YouHit.txt @@ -0,0 +1 @@ +You hit my {0}. diff --git a/77_Salvo/csharp/Resources/YouWon.txt b/77_Salvo/csharp/Resources/YouWon.txt new file mode 100644 index 00000000..1f71343b --- /dev/null +++ b/77_Salvo/csharp/Resources/YouWon.txt @@ -0,0 +1 @@ +You have won. From aa6a06cdc8400789d549b09e67fdb700df1309de Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Sun, 23 Apr 2023 17:55:48 +1000 Subject: [PATCH 118/141] Add SearchPatternShotSelector --- 77_Salvo/csharp/Game.cs | 77 +++++-------------- 77_Salvo/csharp/Resources/Resource.cs | 1 + .../csharp/Targetting/ComputerShotSelector.cs | 2 +- .../csharp/Targetting/HumanShotSelector.cs | 13 ++-- 77_Salvo/csharp/Targetting/SearchPattern.cs | 22 ++++++ .../Targetting/SearchPatternShotSelector.cs | 54 +++++++++++++ 77_Salvo/csharp/Targetting/ShotSelector.cs | 4 +- 7 files changed, 107 insertions(+), 66 deletions(-) create mode 100644 77_Salvo/csharp/Targetting/SearchPattern.cs create mode 100644 77_Salvo/csharp/Targetting/SearchPatternShotSelector.cs diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index 2f3ebe8e..616f0cbc 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -28,7 +28,8 @@ internal class Game } var computerGrid = new Grid(_random); var humanGrid = new Grid(_io); - var humanShotSelector = new HumanShotSelector(humanGrid, computerGrid); + var humanShotSelector = new HumanShotSelector(humanGrid, computerGrid, _io); + var computerShotSelector = new SearchPatternShotSelector(computerGrid, humanGrid, _random); var startResponse = _io.ReadString(Prompts.Start); while (startResponse == Strings.WhereAreYourShips) { @@ -45,7 +46,7 @@ L1930: if (startResponse != "YES") { goto L2620; } L1950: if (startResponse != "YES") { goto L1990; } L1960: turnNumber++; L1980: _io.Write(Strings.Turn(turnNumber)); -L1990: var numberOfShots = humanShotSelector.GetShotCount(); +L1990: var numberOfShots = humanShotSelector.NumberOfShots; L2220: _io.Write(Strings.YouHaveShots(numberOfShots)); if (numberOfShots == 0) { goto L2270; } L2230: if (numberOfShots > computerGrid.UntriedSquareCount) @@ -53,7 +54,7 @@ L2230: if (numberOfShots > computerGrid.UntriedSquareCount) _io.WriteLine(Streams.YouHaveMoreShotsThanSquares); L2250: goto L2890; } - foreach (var shot1 in humanShotSelector.GetShots(_io)) + foreach (var shot1 in humanShotSelector.GetShots()) { if (computerGrid.IsHit(shot1, turnNumber, out var shipName)) { @@ -63,7 +64,7 @@ L2250: goto L2890; L2620: if (startResponse == "YES") { goto L2670; } L2640: turnNumber++; L2660: _io.Write(Strings.Turn(turnNumber)); -L2670: numberOfShots = computerGrid.Ships.Sum(s => s.Shots); +L2670: numberOfShots = computerShotSelector.NumberOfShots; L2840: _io.Write(Strings.IHaveShots(numberOfShots)); L2850: if (humanGrid.UntriedSquareCount > numberOfShots) { goto L2880; } L2860: _io.Write(Streams.IHaveMoreShotsThanSquares); @@ -78,70 +79,28 @@ L2960: for (var i = 1; i <= 12; i++) // if damaged ships L2970: if (hitShipValue[i]>0) { goto L3800; } } -L3000: var shotCount=0; -L3010: var shotAttempts=0; -L3020: var (shot, _) = _random.NextShipPosition(); -L3030: var strategyNumber=0; //RESTORE -L3050: shotAttempts++; -L3060: if (shotAttempts>100) { goto L3010; } - // ensure shot is in range -L3070: shot = shot.BringIntoRange(_random); -L3170: goto L3270; - // record shot -L3180: temp[shotCount]=shot; -L3200: if (shotCount==numberOfShots) { goto L3380; } -L3210: if (strategyNumber==6) { goto L3030; } -L3240: //DATA 1,1,-1,1,1,-3,1,1,0,2,-1,1 - var data = new Offset[] { new(1,1),new(-1,1),new(1,-3),new(1,1),new(0,2),new(-1,1) }; -L3220: //READ X1,Y1 - var offset = data[strategyNumber++]; -L3250: shot+=offset; - // is the shot in range? -L3270: if (!shot.IsInRange) { goto L3210; } - // have we fired here before -L3310: if (humanGrid[shot]>10) { goto L3210; } - // have we already selected this shot? -L3320: for (var i = 1; i <= shotCount; i++) - { -L3330: if (temp[i] == shot) { goto L3210; } - } -L3360: shotCount++; -L3370: goto L3180; + temp = computerShotSelector.GetShots().ToArray(); // display shots -L3380: if (seeShotsResponse != "YES") { goto L3420; } -L3390: for (var i = 1; i <= numberOfShots; i++) +L3380: if (seeShotsResponse == "yes") { -L3400: _io.WriteLine(temp[i]); - } -L3420: for (var i = 1; i <= numberOfShots; i++) - { -L3430: if (humanGrid[temp[i]] == 3) - { - _io.WriteLine("I HIT YOUR BATTLESHIP"); - } - else if (humanGrid[temp[i]] == 2) - { - _io.WriteLine("I HIT YOUR CRUISER"); - } - else if (humanGrid[temp[i]] == 1) - { - _io.WriteLine("I HIT YOUR DESTROYER"); - } - else if (humanGrid[temp[i]] == .5F) - { - _io.WriteLine("I HIT YOUR DESTROYER"); - } - else + foreach (var shot in temp) { - humanGrid[temp[i]]=10+turnNumber; + _io.WriteLine(shot); + } + } + foreach (var shot in temp) + { + if (!humanGrid.IsHit(shot, turnNumber, out var shipName)) + { continue; } + _io.WriteLine(Strings.IHit(shipName)); L3570: for (var j = 1; j <= 12; j++) { // record hit L3580: if (hitTurnRecord[j] != -1) { continue; } L3590: hitTurnRecord[j]=10+turnNumber; -L3600: hitShipValue[j]=humanGrid[temp[i]]; +L3600: hitShipValue[j]=humanGrid[shot]; // look for past hits on same ship L3610: var shipHits=0; L3620: for (var k = 1; k <= 12; k++) @@ -169,7 +128,7 @@ L3760: _io.WriteLine($"{nameof(hitTurnRecord)}( {j} ) = {hitTurnRecord[ L3770: _io.WriteLine($"{nameof(hitShipValue)}( {j} ) = {hitShipValue[j]}"); } return; -L3470: humanGrid[temp[i]]=10+turnNumber; +L3470: humanGrid[shot]=10+turnNumber; } L3490: goto L1950; L3800: //REM************************USINGEARRAY diff --git a/77_Salvo/csharp/Resources/Resource.cs b/77_Salvo/csharp/Resources/Resource.cs index c3a0d8ca..8be52fdb 100644 --- a/77_Salvo/csharp/Resources/Resource.cs +++ b/77_Salvo/csharp/Resources/Resource.cs @@ -21,6 +21,7 @@ internal static class Resource public static string YouHaveShots(int number) => Format(number); public static string IHaveShots(int number) => Format(number); public static string YouHit(string shipName) => Format(shipName); + public static string IHit(string shipName) => Format(shipName); public static string ShotBefore(int turnNumber) => Format(turnNumber); public static string Turn(int number) => Format(number); } diff --git a/77_Salvo/csharp/Targetting/ComputerShotSelector.cs b/77_Salvo/csharp/Targetting/ComputerShotSelector.cs index 1cf2a98a..c8f57d20 100644 --- a/77_Salvo/csharp/Targetting/ComputerShotSelector.cs +++ b/77_Salvo/csharp/Targetting/ComputerShotSelector.cs @@ -1,6 +1,6 @@ namespace Salvo.Targetting; -internal abstract class ComputerShotSelector : ShotSelector +internal class ComputerShotSelector : ShotSelector { private readonly bool _displayShots; diff --git a/77_Salvo/csharp/Targetting/HumanShotSelector.cs b/77_Salvo/csharp/Targetting/HumanShotSelector.cs index 6a50330a..40812926 100644 --- a/77_Salvo/csharp/Targetting/HumanShotSelector.cs +++ b/77_Salvo/csharp/Targetting/HumanShotSelector.cs @@ -2,23 +2,26 @@ namespace Salvo.Targetting; internal class HumanShotSelector : ShotSelector { - public HumanShotSelector(Grid source, Grid target) + private readonly IReadWrite _io; + + internal HumanShotSelector(Grid source, Grid target, IReadWrite io) : base(source, target) { + _io = io; } - public IEnumerable GetShots(IReadWrite io) + internal override IEnumerable GetShots() { - var shots = new Position[GetShotCount()]; + var shots = new Position[NumberOfShots()]; for (var i = 0; i < shots.Length; i++) { while (true) { - var position = io.ReadValidPosition(); + var position = _io.ReadValidPosition(); if (Target.WasTargetedAt(position, out var turnTargeted)) { - io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {turnTargeted}"); + _io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {turnTargeted}"); continue; } shots[i] = position; diff --git a/77_Salvo/csharp/Targetting/SearchPattern.cs b/77_Salvo/csharp/Targetting/SearchPattern.cs new file mode 100644 index 00000000..ac2071c8 --- /dev/null +++ b/77_Salvo/csharp/Targetting/SearchPattern.cs @@ -0,0 +1,22 @@ +using System.Collections.Immutable; + +namespace Salvo.Targetting; + +internal class SearchPattern +{ + private static readonly ImmutableArray _offsets = + ImmutableArray.Create(new(1, 1), new(-1, 1), new(1, -3), new(1, 1), new(0, 2), new(-1, 1)); + + private int _nextIndex; + + internal bool TryGetOffset(out Offset offset) + { + offset = default; + if (_nextIndex >= _offsets.Length) { return false; } + + offset = _offsets[_nextIndex++]; + return true; + } + + internal void Reset() => _nextIndex = 0; +} \ No newline at end of file diff --git a/77_Salvo/csharp/Targetting/SearchPatternShotSelector.cs b/77_Salvo/csharp/Targetting/SearchPatternShotSelector.cs new file mode 100644 index 00000000..4e3fcafb --- /dev/null +++ b/77_Salvo/csharp/Targetting/SearchPatternShotSelector.cs @@ -0,0 +1,54 @@ +namespace Salvo.Targetting; + +internal class SearchPatternShotSelector : ShotSelector +{ + private const int MaxSearchPatternAttempts = 100; + private readonly IRandom _random; + private readonly SearchPattern _searchPattern = new(); + private readonly List _shots = new(); + + internal SearchPatternShotSelector(Grid source, Grid target, IRandom random) + : base(source, target) + { + _random = random; + } + + internal override IEnumerable GetShots() + { + while(_shots.Count < NumberOfShots) + { + var (seed, _) = _random.NextShipPosition(); + SearchFrom(seed); + } + return _shots; + } + + private void SearchFrom(Position candidateShot) + { + var attemptsLeft = MaxSearchPatternAttempts; + while (true) + { + _searchPattern.Reset(); + if (attemptsLeft-- == 0) { return; } + candidateShot = candidateShot.BringIntoRange(_random); + if (FindValidShots(candidateShot)) { return; } + } + } + + private bool FindValidShots(Position candidateShot) + { + while (true) + { + if (IsValidShot(candidateShot)) + { + _shots.Add(candidateShot); + if (_shots.Count == NumberOfShots) { return true; } + } + if (!_searchPattern.TryGetOffset(out var offset)) { return false; } + candidateShot += offset; + } + } + + private bool IsValidShot(Position candidate) + => candidate.IsInRange && !Target.WasTargetedAt(candidate, out _) && !_shots.Contains(candidate); +} \ No newline at end of file diff --git a/77_Salvo/csharp/Targetting/ShotSelector.cs b/77_Salvo/csharp/Targetting/ShotSelector.cs index fae30f31..5aaa4753 100644 --- a/77_Salvo/csharp/Targetting/ShotSelector.cs +++ b/77_Salvo/csharp/Targetting/ShotSelector.cs @@ -11,5 +11,7 @@ internal abstract class ShotSelector protected Grid Source { get; } protected Grid Target { get; } - public int GetShotCount() => Source.Ships.Sum(s => s.Shots); + internal int NumberOfShots => Source.Ships.Sum(s => s.Shots); + + internal abstract IEnumerable GetShots(); } From e0791fa39576832b86b2d1775cf22c2007ce5b33 Mon Sep 17 00:00:00 2001 From: kbrannen Date: Sun, 23 Apr 2023 13:50:49 -0500 Subject: [PATCH 119/141] added 14_bowling for perl --- 14_Bowling/perl/README.md | 19 +++ 14_Bowling/perl/bowling.pl | 254 +++++++++++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+) create mode 100755 14_Bowling/perl/bowling.pl diff --git a/14_Bowling/perl/README.md b/14_Bowling/perl/README.md index e69c8b81..9e030cb0 100644 --- a/14_Bowling/perl/README.md +++ b/14_Bowling/perl/README.md @@ -1,3 +1,22 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) Conversion to [Perl](https://www.perl.org/) + +###Bowling program in Perl + +Run normally, this is a fairly faithful translation of the Basic game. +The only real differences are a few trivial fix-ups on the prints to make it +look better, and the player/frame/ball line was put before the "get the ball +going" line to make it more obvious who's turn it is. + +However, if you run it with "-a" on the command line, it will go into +"advanced" mode, which means that "." is used to show pin down and "!" for +pin up, current running scores are shown at the end of each frame, and the +scoring also looks more normal at the end. This is all done because I think it +looks better and I wanted to see a score. Having a flag says you can play +whichever version of the game you like. + +Note, the original code doesn't do the 10th frame correctly, in that it will +never do more than 2 balls, so the best score you can get is a 290. +This is true in both modes. That being said, it will always give you a mediocre +game; I don't think I've ever seen a score over 140. diff --git a/14_Bowling/perl/bowling.pl b/14_Bowling/perl/bowling.pl new file mode 100755 index 00000000..1a00cd6b --- /dev/null +++ b/14_Bowling/perl/bowling.pl @@ -0,0 +1,254 @@ +#!/usr/bin/perl + +# Bowling program in Perl +# Run normally, this is a fairly faithful translation of the Basic game. +# The only real differences are a few trivial fix-ups on the prints to make it +# look better, and the player/frame/ball line was put before the "get the ball +# going" line to make it more obvious who's turn it is. +# +# However, if you run it with "-a" on the command line, it will go into +# 'advanced' mode, which means that "." is used to show pin down and "!" for +# pin up, current running scores are shown at the end of each frame, and the +# scoring also looks more normal at the end. This is all done because I think it +# looks better and I wanted to see a score. Having a flag says you can play +# whichever version of the game you like. +# +# Note, the original code doesn't do the 10th frame correctly, in that it will +# never do more than 2 balls, so the best score you can get is a 290. +# This is true in both modes. That being said, it will always give you a mediocre game. +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +print "\n"; +print " " x 34, "BASKETBALL\n"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; + +# globals +my @C; # pin position matraix? +my @Scores; # scores: [player num][frame][ball] # ball3 is end-result, ball4 is frame score (advanced mode) +my $Answer; # get answers +my $Num_players; # number of players +my $Advanced = 0; # flag, 1 to use advanced code, 0 for original code +my $char_down = '0'; # char to show pin is down +my $char_up = '+'; # char to show pin is standing + +if ($ARGV[0] && $ARGV[0] eq "-a") +{ + shift; + $Advanced = 1; + $char_down = '.'; + $char_up = '!'; +} + +print "WELCOME TO THE ALLEY\n"; +print "BRING YOUR FRIENDS\n"; +print "OKAY LET'S FIRST GET ACQUAINTED\n\n"; +print "SEE THE INSTRUCTIONS (Y/N): "; +chomp($Answer = uc(<>)); +if ($Answer eq "Y") +{ + print "THE GAME OF BOWLING TAKES MIND AND SKILL. DURING THE GAME\n"; + print "THE COMPUTER WILL KEEP SCORE.YOU MAY COMPETE WITH\n"; + print "OTHER PLAYERS[UP TO FOUR]. YOU WILL BE PLAYING TEN FRAMES\n"; + print "ON THE PIN DIAGRAM 'O' MEANS THE PIN IS DOWN...'+' MEANS THE\n"; + print "PIN IS STANDING. AFTER THE GAME THE COMPUTER WILL SHOW YOUR SCORES.\n"; +} + +do { + print "FIRST OF ALL...HOW MANY ARE PLAYING (1-4): "; + $Num_players = int(<>); +} while ($Num_players < 1 || $Num_players > 4); + +print "\nVERY GOOD...\n"; + +while (1) +{ + # reset all scores + for my $p (1 .. $Num_players) # players + { + for my $f (1 .. 10) # frames + { + for my $b (1 .. 3) # balls + { + $Scores[$p][$f][$b] = 0; + } + } + } + + # play the game + for my $frame (1 .. 10) # frame + { + for my $curr_player (1 .. $Num_players) # player + { + my $last_pins=0; # pins down for last ball + my $ball=1; # ball number, 1 or 2 + my $end_result=0; # result at end of turn: 3=strike, 2=spare, 1=pins-left + for my $i (1 .. 15) { $C[$i] = 0 } + + while (1) # another ball + { + # ARK BALL GENERATOR USING MOD '15' SYSTEM + my $K=0; + my $curr_pins=0; # pins down for this ball + for my $i (1 .. 20) + { + my $x = int(rand(1) * 100); + my $j; + for ($j=1 ; $j <= 10 ; $j++) + { + last if ($x < 15 * $j); + } + $C[15 * $j - $x] = 1; + } + + # ARK PIN DIAGRAM + print "PLAYER: $curr_player FRAME: $frame BALL: $ball\n"; + print "PRESS ENTER TO GET THE BALL GOING."; + $Answer = <>; # not used, just need an enter + for my $i (0 .. 3) + { + print "\n"; + print " " x $i; # avoid the TAB(), just shift each row over for the triangle + for my $j (1 .. 4 - $i) + { + $K++; + print ($C[$K] == 1 ? " $char_down" : " $char_up"); + } + } + print "\n"; + + # ARK ROLL ANALYSIS + for my $i (1 .. 10) + { + $curr_pins += $C[$i]; + } + if ($curr_pins - $last_pins == 0) + { + print "GUTTER!!\n"; + } + if ($ball == 1 && $curr_pins == 10) + { + print "STRIKE!!!!!\a\a\a\a\n"; # \a is for bell + $end_result = 3; + } + elsif ($ball == 2 && $curr_pins == 10) + { + print "SPARE!!!!\n"; + $end_result = 2; + } + elsif ($ball == 2 && $curr_pins < 10) + { + if ($Advanced) { print 10 - $curr_pins, " PENS LEFT!!!\n"; } + else { print "ERROR!!!\n"; } + $end_result = 1; + } + if ($ball == 1 && $curr_pins < 10) + { + print "ROLL YOUR 2ND BALL\n"; + } + print "\n"; + + # ARK STORAGE OF THE SCORES + if ($Advanced) { $Scores[$curr_player][$frame][$ball] = $curr_pins - $last_pins; } + else { $Scores[$curr_player][$frame][$ball] = $curr_pins; } + if ($ball == 1) + { + $ball = 2; + $last_pins = $curr_pins; + + if ($end_result == 3) # strike, no more rolls, goto last + { + $Scores[$curr_player][$frame][$ball] = $curr_pins; + } + else + { + $Scores[$curr_player][$frame][$ball] = $curr_pins - $last_pins; + next if ($end_result == 0); # next roll + } + } + last; + } + $Scores[$curr_player][$frame][3] = $end_result; + } # next player + if ($Advanced) + { + print "Scores:\n"; + for my $p (1 .. $Num_players) + { + my $total = calc_score($p); + print "\tPlayer $p: $total\n"; + } + print "\n"; + } + } # next frame + + # end of game, show full scoreboard + show_scoreboard(); + + print "DO YOU WANT ANOTHER GAME (Y/N): "; + chomp($Answer = uc(<>)); + print "\n"; + last if ($Answer ne "Y"); +} +exit(0); + +sub show_scoreboard +{ + print "FRAMES\n"; + for my $i (1 .. 10) + { + print " $i "; + } + print "\n"; + my @results = ( "-", ".", "/", "X" ); + for my $p (1 .. $Num_players) + { + print "Player $p\n" if ($Advanced); + my $ball_max = ($Advanced ? 4 : 3); + for my $b (1 .. $ball_max) + { + for my $f (1 .. 10) + { + if ($b != 3) { print sprintf("%2d ", $Scores[$p][$f][$b]); } + else { print sprintf("%2s ", $results[$Scores[$p][$f][$b]]); } + } + print "\n"; + } + print "\n"; + } +} + +sub calc_score +{ + my $player = shift; + my $total = 0; + for my $frame (1 .. 10) + { + my $score = 0; + if ($frame == 10 || $Scores[$player][$frame][3] == 1) # pins + { + $score = $Scores[$player][$frame][1] + $Scores[$player][$frame][2]; + } + elsif ($Scores[$player][$frame][3] == 2) # spare + { + $score = 10 + $Scores[$player][$frame+1][1]; + } + elsif ($Scores[$player][$frame][3] == 3) # strike + { + $score = 10 + $Scores[$player][$frame+1][1]; + if ($Scores[$player][$frame+1][1] == 10) + { + $score += ($frame < 9 ? $Scores[$player][$frame+2][1] : $Scores[$player][$frame+1][2]); + } + else + { + $score += $Scores[$player][$frame+1][2]; + } + } + $Scores[$player][$frame][4] = $score; + $total += $score; + } + return $total; +} From 35124a2287831019710e726713b92bc65214eb4a Mon Sep 17 00:00:00 2001 From: kbrannen Date: Sun, 23 Apr 2023 15:39:00 -0500 Subject: [PATCH 120/141] added 34_Digits for perl --- 34_Digits/perl/digits.pl | 125 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100755 34_Digits/perl/digits.pl diff --git a/34_Digits/perl/digits.pl b/34_Digits/perl/digits.pl new file mode 100755 index 00000000..2f49bd0e --- /dev/null +++ b/34_Digits/perl/digits.pl @@ -0,0 +1,125 @@ +#!/usr/bin/perl + +# Digits program in Perl +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# globals +my $Answer; + +print "\n"; +print " " x 33, "DIGITS"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; + +print "THIS IS A GAME OF GUESSING.\n"; +print "FOR INSTRUCTIONS, TYPE '1', ELSE TYPE '0': "; +chomp($Answer = <>); +if ($Answer == 1) +{ + print "\nPLEASE TAKE A PIECE OF PAPER AND WRITE DOWN\n"; + print "THE DIGITS '0', '1', OR '2' THIRTY TIMES AT RANDOM.\n"; + print "ARRANGE THEM IN THREE LINES OF TEN DIGITS EACH.\n"; + print "I WILL ASK FOR THEN TEN AT A TIME.\n"; + print "I WILL ALWAYS GUESS THEM FIRST AND THEN LOOK AT YOUR\n"; + print "NEXT NUMBER TO SEE IF I WAS RIGHT. BY PURE LUCK,\n"; + print "I OUGHT TO BE RIGHT TEN TIMES. BUT I HOPE TO DO BETTER\n"; + print "THAN THAT. *****\n\n\n"; +} + +my ($A, $B, $C) = (0, 1, 3); +my (@M, @K, @L); # DIM M(26,2),K(2,2),L(8,2) +while (1) +{ + for my $i (0 .. 26) { for my $j (0 .. 2) { $M[$i][$j] = 1; } } + for my $i (0 .. 2) { for my $j (0 .. 2) { $K[$i][$j] = 9; } } + for my $i (0 .. 8) { for my $j (0 .. 2) { $L[$i][$j] = 3; } } + $L[0][0] = $L[4][1] = $L[8][2] = 2; + my $Z = 26; + my $Z1 = 8; + my $Z2 = 2; + my $X = 0; + my @N; + + for my $T (1 .. 3) + { + my $have_input = 0; + while (!$have_input) + { + $have_input = 1; + print "\nTEN NUMBERS, PLEASE: "; + chomp($Answer = <>); + $Answer = "0 " . $Answer; # need to be 1-based, so prepend a throw-away value for [0] + @N = split(/\s+/, $Answer); + for my $i (1 .. 10) + { + if (!defined($N[$i]) || ($N[$i] != 0 && $N[$i] != 1 && $N[$i] != 2)) + { + print "ONLY USE THE DIGITS '0', '1', OR '2'.\n"; + print "LET'S TRY AGAIN."; + $have_input = 0; + last; + } + } + } + + print "\nMY GUESS\tYOUR NO.\tRESULT\tNO. RIGHT\n\n"; + for my $U (1 .. 10) + { + my $num = $N[$U]; + my $S = 0; + my $G; + for my $J (0 .. 2) + { + my $S1 = $A * $K[$Z2][$J] + $B * $L[$Z1][$J] + $C * $M[$Z][$J]; + next if ($S > $S1); + if ($S >= $S1) + { + next if (rand(1) < .5); + } + $S = $S1; + $G = $J; + } # NEXT J + print " $G\t\t$N[$U]\t\t"; + if ($G == $N[$U]) + { + $X++; + print "RIGHT\t$X\n"; + $M[$Z][$num]++; + $L[$Z1][$num]++; + $K[$Z2][$num]++; + $Z -= int($Z / 9) * 9; + $Z = 3 * $Z + $N[$U]; + } + else + { + print "WRONG\t$X\n"; + } + $Z1 = $Z - int($Z / 9) * 9; + $Z2 = $N[$U]; + } # NEXT U + } # NEXT T + + print "\n"; + if ($X == 10) + { + print "I GUESSED EXACTLY 1/3 OF YOUR NUMBERS.\n"; + print "IT'S A TIE GAME.\n"; + } + elsif ($X > 10) + { + print "I GUESSED MORE THAN 1/3, OR $X, OF YOUR NUMBERS.\n"; + print "I WIN.\a\a\a\a\a\a\a\a\a\a" + } + else + { + print "I GUESSED LESS THAN 1/3, OR $X, OF YOUR NUMBERS.\n"; + print "YOU BEAT ME. CONGRATULATIONS *****\n"; + } + + print "\nDO YOU WANT TO TRY AGAIN (1 FOR YES, 0 FOR NO): "; + chomp($Answer = <>); + last if ($Answer != 1); +} +print "\nTHANKS FOR THE GAME.\n"; From 4a26fcc95568ad7772297e019da5142b18cfcd8b Mon Sep 17 00:00:00 2001 From: kbrannen Date: Sun, 23 Apr 2023 22:02:16 -0500 Subject: [PATCH 121/141] added 35-flipflop for perl --- 36_Flip_Flop/perl/flipflop.pl | 124 ++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100755 36_Flip_Flop/perl/flipflop.pl diff --git a/36_Flip_Flop/perl/flipflop.pl b/36_Flip_Flop/perl/flipflop.pl new file mode 100755 index 00000000..63e3f84e --- /dev/null +++ b/36_Flip_Flop/perl/flipflop.pl @@ -0,0 +1,124 @@ +#!/usr/bin/perl + +# Flip Flop program in Perl +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; +use Math::Trig; + +print "\n"; +print " " x 32, "FLIPFLOP"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; +# *** CREATED BY MICHAEL CASS + +print "THE OBJECT OF THIS PUZZLE IS TO CHANGE THIS:\n\n"; +print "X X X X X X X X X X\n\n"; +print "TO THIS:\n\n"; +print "O O O O O O O O O O\n\n"; +print "BY TYPING THE NUMBER CORRESPONDING TO THE POSITION OF THE\n"; +print "LETTER ON SOME NUMBERS, ONE POSITION WILL CHANGE, ON\n"; +print "OTHERS, TWO WILL CHANGE. TO RESET LINE TO ALL X'S, TYPE 0\n"; +print "(ZERO) AND TO START OVER IN THE MIDDLE OF A GAME, TYPE \n"; +print "11 (ELEVEN).\n\n"; + +sub initialize +{ + my @a; + print "1 2 3 4 5 6 7 8 9 10\n"; + print "X X X X X X X X X X\n"; + for my $i (0 .. 10) { $a[$i] = "X"; } # make sure [0] has a value just in case + return @a; +} + +while (1) +{ + my $Q = rand(1); + my $C = 0; + print "HERE IS THE STARTING LINE OF X'S.\n\n"; + my @A = initialize(); + + while (1) + { + my $M = 0; + my $N; + while (1) + { + print "\nINPUT THE NUMBER: "; + chomp($N = <>); + if ($N != int($N) || $N < 0 || $N > 11) + { + print "ILLEGAL ENTRY--TRY AGAIN.\n"; + next; + } + last; + } + if ($N == 11) # start a new game + { + print "\n\n"; + last; + } + if ($N == 0) # reset line + { + @A = initialize(); + next; + } + + if ($M != $N) + { + $M = $N; + $A[$N] = ($A[$N] eq "O") ? "X" : "O"; + while ($M == $N) + { + my $R = tan($Q + $N / $Q - $N) - sin($Q / $N) + 336 * sin(8 * $N); + $N = $R - int($R); + $N = int(10 * $N); + if ($A[$N] eq "O") + { + $A[$N] = "X"; + next; + } + $A[$N] = "O"; + last; # GOTO 610 + + $A[$N] = "X"; + } + } + else + { + if ($A[$N] ne "O") { $A[$N] = "O"; } + while ($M == $N) + { + my $R = .592 * (1 / tan($Q / $N + $Q)) / sin( $N * 2 + $Q) - cos($N); + $N = $R - int($R); + $N = int(10 * $N); + if ($A[$N] eq "O") + { + $A[$N] = "X"; + next; + } + $A[$N] = "O"; + last; + } + } + + print "1 2 3 4 5 6 7 8 9 10\n"; + for my $i (1 .. 10) { print "$A[$i] "; } + print "\n"; + $C++; + my $i; + for ($i=1 ; $i <= 10 ; $i++) { + last if ($A[$i] ne "O"); + } + if ($i == 11) + { + if ($C <= 12) { print "VERY GOOD. YOU GUESSED IT IN ONLY $C GUESSES.\n"; } + else { print "TRY HARDER NEXT TIME. IT TOOK YOU $C GUESSES.\n"; } + last; + } + } + print "DO YOU WANT TO TRY ANOTHER PUZZLE (Y/N): "; + $_ = <>; + print "\n"; + last if (m/^n/i); +} From fbd8aa72e1cc1c8ca5241ac13a1b630755573bc7 Mon Sep 17 00:00:00 2001 From: Chuck Jordan Date: Sat, 29 Apr 2023 19:40:42 -0700 Subject: [PATCH 122/141] Add Lua port of 03_Animal --- 03_Animal/lua/Animal.lua | 181 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 03_Animal/lua/Animal.lua diff --git a/03_Animal/lua/Animal.lua b/03_Animal/lua/Animal.lua new file mode 100644 index 00000000..293c53e4 --- /dev/null +++ b/03_Animal/lua/Animal.lua @@ -0,0 +1,181 @@ +-- +-- Animal.lua +-- + +-- maintain a flat table/list of all the known animals +local animals = { + "FISH", + "BIRD" +} + +-- store the questions as a binary tree with each node having a +-- "y" or "n" branch +-- if the node has a member named "a" it's an answer, with an index +-- into the animals list. +-- Otherwise, it's a question that leads to more nodes. +local questionTree = { + q = "DOES IT SWIM", + y = { a = 1 }, + n = { a = 2 } +} + +-- print the given prompt string and then wait for input +-- loops until a non-empty input is given +-- returns the input as an upper-case string +function askPrompt(promptString) + local answered = false + local a + while (not answered) do + print(promptString) + a = io.read() + a = string.upper(a) + if (string.len(a) > 0) then + answered = true + end + end + return a +end + +-- print the given prompt string and then wait for the +-- user to enter a string beginning with "Y" or "N" +function askYesOrNo(promptString) + local a + while ((a ~= "Y") and (a ~= "N")) do + a = askPrompt(promptString) + a = a:sub(1,1) + end + return a +end + +-- prints the introductory text from the original BASIC program +function printIntro() + print(string.format("%32s", " ") .. "ANIMAL") + print(string.format("%15s", " ") .. "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY") + print() + print() + print() + print("PLAY 'GUESS THE ANIMAL'") + print("THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT.") + print() +end + +-- prints the animals known in the source list +function listKnownAnimals() + print() + print("ANIMALS I ALREADY KNOW ARE:") + + local x + local item + for x = 1,#animals do + -- use string.format to space each animal in a 12-character-wide "cell" + item = string.format("%-12s", animals[x]) + + -- io.write() works like print(), but doesn't automatically add a carriage return/newline + io.write(item) + + -- every fifth item, start a new line + if ((x % 5) == 0) then + io.write("\n") + end + end + + print() + print() +end + +-- Prompts the user for info about the animal they were thinking of, then +-- uses that to add a new branch to the tree +-- curNode: the node in the tree where the computer made a wrong guess +-- branch: the answer the user gave to curNode's question +function addAnimalToTree(curNode, branch) + local newAnimal + local curResponse = curNode[branch] + local guessedIndex = curResponse.a + local guessedAnimal = animals[guessedIndex] + local newQuestion, newAnswer, newIndex + local newNode + + newAnimal = askPrompt("THE ANIMAL YOU WERE THINKING OF WAS A ?") + newQuestion = askPrompt("PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A ".. + tostring(newAnimal).." FROM A "..tostring(guessedAnimal)) + newAnswer = askYesOrNo("FOR A "..tostring(newAnimal).." THE ANSWER WOULD BE?") + + -- add the new animal to the master list at the end, and + -- save off its index in the list + table.insert(animals, newAnimal) + newIndex = #animals + + -- create a new node for the question we just learned + newNode = {} + newNode.q = newQuestion + if (newAnswer == "Y") then + newNode.y = { a = newIndex } + newNode.n = { a = guessedIndex } + else + newNode.y = { a = guessedIndex } + newNode.n = { a = newIndex } + end + + -- replace the previous answer with our new node + curNode[branch] = newNode +end + +-- Starts at the root of the question tree and asks questions about +-- the user's animal until the computer hits an "a" answer node and tries +-- to make a guess +function askAboutAnimal() + local curNode = questionTree + local finished = false + local response, responseIndex + local nextNode, animalName + while (not finished) do + response = askYesOrNo(curNode.q .. "?") + + -- convert the response "Y" or "N" to the lowercase "y" or "n" that we use to name our branches + branch = string.lower(response) + nextNode = curNode[branch] + + -- is the next node an answer node, or another question? + if (nextNode.a ~= nil) then + -- it's an answer, so make a guess + animalName = animals[nextNode.a] + response = askYesOrNo("IS IT A "..tostring(animalName).."?") + if (response == "Y") then + -- we got the correct answer, so prompt for a new animal + print() + print("WHY NOT TRY ANOTHER ANIMAL?") + else + -- incorrect answer, so add a new entry at this point in the tree + addAnimalToTree(curNode, branch) + end + + -- whether we were right or wrong, we're finished with this round + finished = true + else + -- it's another question, so advance down the tree + curNode = nextNode + end + end +end + +-- MAIN CONTROL SECTION + +printIntro() + +-- loop forever until the player requests an exit by entering a blank line +local exitRequested = false +local answer + +while (not exitRequested) do + print("ARE YOU THINKING OF AN ANIMAL?") + answer = io.read() + answer = string.upper(answer) + + if (string.len(answer) == 0) then + exitRequested = true + elseif (answer:sub(1,4) == "LIST") then + listKnownAnimals() + elseif (answer:sub(1,1) == "Y") then + askAboutAnimal() + end +end From edb3acd3edf24eb4cee7c2008c2308e9d2c3767f Mon Sep 17 00:00:00 2001 From: kbrannen Date: Sat, 6 May 2023 02:42:07 -0500 Subject: [PATCH 123/141] added 38-furtrader for perl --- 38_Fur_Trader/perl/README.md | 2 + 38_Fur_Trader/perl/furtrader.pl | 265 ++++++++++++++++++++++++++++++++ 2 files changed, 267 insertions(+) create mode 100755 38_Fur_Trader/perl/furtrader.pl diff --git a/38_Fur_Trader/perl/README.md b/38_Fur_Trader/perl/README.md index e69c8b81..c7001be7 100644 --- a/38_Fur_Trader/perl/README.md +++ b/38_Fur_Trader/perl/README.md @@ -1,3 +1,5 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) Conversion to [Perl](https://www.perl.org/) + +You can answer yes/no questions in lower case if desired. diff --git a/38_Fur_Trader/perl/furtrader.pl b/38_Fur_Trader/perl/furtrader.pl new file mode 100755 index 00000000..f1e6b01a --- /dev/null +++ b/38_Fur_Trader/perl/furtrader.pl @@ -0,0 +1,265 @@ +#!/usr/bin/perl + +# Fur Trader program in Perl +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# globals +my @Pelts = (qw(0 MINK BEAVER ERMINE FOX )); +my $Num_pelts = 4; +my @Quantity; # how many of each fur +my $Money; +my $Max_pelts = 190; +my $Ermine_price; # like we have @Pelts and @Quantity we could have @Prices +my $Beaver_price; # then have 4 constants as index into the arrays to avoid +my $Fox_price; # the magic numbers 1-4, or better have a array of objects (really a hash) +my $Mink_price; # with the 3 keys (name, number, price), but well keep it + # with 4 vars like the basic program did + +print "\n"; +print " " x 31, "FUR TRADER\n"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; + +init(); +while (1) +{ + my $ans = get_yesno(); + last if ($ans ne "YES"); + + $Ermine_price = new_price(.15, 0.95); + $Beaver_price = new_price(.25, 1.00); + + print "\nYOU HAVE \$$Money SAVINGS.\n"; + print "AND $Max_pelts FURS TO BEGIN THE EXPEDITION.\n"; + print "\nYOUR $Max_pelts FURS ARE DISTRIBUTED AMONG THE FOLLOWING\n"; + print "KINDS OF PELTS: MINK, BEAVER, ERMINE AND FOX.\n"; + reset_furs(); + + my $total = 0; + for my $j ( 1 .. $Num_pelts) + { + print "\nHOW MANY $Pelts[$j] PELTS DO YOU HAVE? "; + chomp(my $ans = <>); + $Quantity[$j] = int($ans); + $total += $Quantity[$j]; + } + if ($total > $Max_pelts) + { + print "\nYOU MAY NOT HAVE THAT MANY FURS.\n"; + print "DO NOT TRY TO CHEAT. I CAN ADD.\n"; + print "YOU MUST START AGAIN.\n"; + init(); + next; + } + + print "\nYOU MAY TRADE YOUR FURS AT FORT 1, FORT 2,\n"; + print "OR FORT 3. FORT 1 IS FORT HOCHELAGA (MONTREAL)\n"; + print "AND IS UNDER THE PROTECTION OF THE FRENCH ARMY.\n"; + print "FORT 2 IS FORT STADACONA (QUEBEC) AND IS UNDER THE\n"; + print "PROTECTION OF THE FRENCH ARMY. HOWEVER, YOU MUST\n"; + print "MAKE A PORTAGE AND CROSS THE LACHINE RAPIDS.\n"; + print "FORT 3 IS FORT NEW YORK AND IS UNDER DUTCH CONTROL.\n"; + print "YOU MUST CROSS THROUGH IROQUOIS LAND.\n"; + my $done = 0; + while (!$done) + { + $ans = 0; + while ($ans < 1 || $ans > 3) + { + no warnings; # in case user enters alpha chars, then int() will return 0 with no warnings + print "ANSWER 1, 2, OR 3: "; + $ans = int(<>); + } + # returns 0 if they want to go somewhere else; + # or returns the old basic line number to show what to do + if ($ans == 1) { $done = fort1(); } + elsif ($ans == 2) { $done = fort2(); } + elsif ($ans == 3) { $done = fort3(); } + } + + if ($done == 1410) + { + print "YOUR BEAVER SOLD FOR \$", $Beaver_price * $Quantity[2], "\t"; + } + + if ($done <= 1414) + { + print "YOUR FOX SOLD FOR \$", $Fox_price * $Quantity[4], "\n"; + print "YOUR ERMINE SOLD FOR \$", $Ermine_price * $Quantity[3], "\t"; + print "YOUR MINK SOLD FOR \$", $Mink_price * $Quantity[1], "\n"; + } + + # 1418 is always done + $Money += $Mink_price * $Quantity[1] + $Beaver_price * $Quantity[2] + $Ermine_price * $Quantity[3] + $Fox_price * $Quantity[4]; + print "\nYOU NOW HAVE \$$Money INCLUDING YOUR PREVIOUS SAVINGS\n"; + print "\nDO YOU WANT TO TRADE FURS NEXT YEAR? "; +} +exit(0); + +############################################################### + +sub init +{ + print "YOU ARE THE LEADER OF A FRENCH FUR TRADING EXPEDITION IN\n"; + print "1776 LEAVING THE LAKE ONTARIO AREA TO SELL FURS AND GET\n"; + print "SUPPLIES FOR THE NEXT YEAR. YOU HAVE A CHOICE OF THREE\n"; + print "FORTS AT WHICH YOU MAY TRADE. THE COST OF SUPPLIES\n"; + print "AND THE AMOUNT YOU RECEIVE FOR YOUR FURS WILL DEPEND\n"; + print "ON THE FORT THAT YOU CHOOSE.\n"; + + $Money = 600; + print "DO YOU WISH TO TRADE FURS?\n"; +} + +sub new_price +{ + my ($base, $factor) = @_; + return int(($base * rand(1) + $factor) * 100 + .5) / 100; +} + +sub supplies_fs +{ + print "SUPPLIES AT FORT STADACONA COST \$125.00.\n"; + print "YOUR TRAVEL EXPENSES TO STADACONA WERE \$15.00.\n"; +} + +sub supplies_ny +{ + print "SUPPLIES AT NEW YORK COST \$80.00.\n"; + print "YOUR TRAVEL EXPENSES TO NEW YORK WERE \$25.00.\n"; +} + +sub reset_furs +{ + for my $j (1 .. $Num_pelts) { $Quantity[$j] = 0; } +} + +sub get_yesno +{ + my $ans; + print "ANSWER YES OR NO: "; + chomp($ans = uc(<>)); + return $ans; +} + +sub trade_elsewhere +{ + print "DO YOU WANT TO TRADE AT ANOTHER FORT? "; + my $ans = get_yesno(); + return $ans; +} + +sub fort1 +{ + print "\nYOU HAVE CHOSEN THE EASIEST ROUTE. HOWEVER, THE FORT\n"; + print "IS FAR FROM ANY SEAPORT. THE VALUE\n"; + print "YOU RECEIVE FOR YOUR FURS WILL BE LOW AND THE COST\n"; + print "OF SUPPLIES HIGHER THAN AT FORTS STADACONA OR NEW YORK.\n"; + my $ans = trade_elsewhere(); + if ($ans eq "YES") { return 0; } + + $Money -= 160; + $Mink_price = new_price(.2, .7 ); + $Ermine_price = new_price(.2, .65); + $Beaver_price = new_price(.2, .75); + $Fox_price = new_price(.2, .8 ); + print "\nSUPPLIES AT FORT HOCHELAGA COST \$150.00.\n"; + print "YOUR TRAVEL EXPENSES TO HOCHELAGA WERE \$10.00.\n"; + return 1410; +} + +sub fort2 +{ + print "\nYOU HAVE CHOSEN A HARD ROUTE. IT IS, IN COMPARSION,\n"; + print "HARDER THAN THE ROUTE TO HOCHELAGA BUT EASIER THAN\n"; + print "THE ROUTE TO NEW YORK. YOU WILL RECEIVE AN AVERAGE VALUE\n"; + print "FOR YOUR FURS AND THE COST OF YOUR SUPPLIES WILL BE AVERAGE.\n"; + my $ans = trade_elsewhere(); + if ($ans eq "YES") { return 0; } + + $Money -= 140; + print "\n"; + $Mink_price = new_price(.3, .85); + $Ermine_price = new_price(.15, .8); + $Beaver_price = new_price(.2, .9); + my $P = int(10 * rand(1)) + 1; + if ($P <= 2) + { + $Quantity[2] = 0; + print "YOUR BEAVER WERE TOO HEAVY TO CARRY ACROSS\n"; + print "THE PORTAGE. YOU HAD TO LEAVE THE PELTS, BUT FOUND\n"; + print "THEM STOLEN WHEN YOU RETURNED.\n"; + supplies_fs(); + return 1414; + } + elsif ($P <= 6) + { + print "YOU ARRIVED SAFELY AT FORT STADACONA.\n"; + supplies_fs(); + } + elsif ($P <= 8) + { + reset_furs(); + print "YOUR CANOE UPSET IN THE LACHINE RAPIDS. YOU\n"; + print "LOST ALL YOUR FURS.\n"; + supplies_fs(); + return 1418; + } + elsif ($P <= 10) + { + $Quantity[4] = 0; + print "YOUR FOX PELTS WERE NOT CURED PROPERLY.\n"; + print "NO ONE WILL BUY THEM.\n"; + supplies_fs(); + } + return 1410; +} + +sub fort3 +{ + print "\nYOU HAVE CHOSEN THE MOST DIFFICULT ROUTE. AT\n"; + print "FORT NEW YORK YOU WILL RECEIVE THE HIGHEST VALUE\n"; + print "FOR YOUR FURS. THE COST OF YOUR SUPPLIES\n"; + print "WILL BE LOWER THAN AT ALL THE OTHER FORTS.\n"; + my $ans = trade_elsewhere(); + if ($ans eq "YES") { return 0; } + + $Money -= 105; + print "\n"; + $Mink_price = new_price(.15, 1.05); + $Fox_price = new_price(.25, 1.1); + $Fox_price = new_price(.25, 1.1); + my $P = int(10 * rand(1)) + 1; + if ($P <= 2) + { + print "YOU WERE ATTACKED BY A PARTY OF IROQUOIS.\n"; + print "ALL PEOPLE IN YOUR TRADING GROUP WERE\n"; + print "KILLED. THIS ENDS THE GAME.\n"; + exit(0); + } + elsif ($P <= 6) + { + print "YOU WERE LUCKY. YOU ARRIVED SAFELY\n"; + print "AT FORT NEW YORK.\n"; + supplies_ny(); + } + elsif ($P <= 8) + { + reset_furs(); + print "YOU NARROWLY ESCAPED AN IROQUOIS RAIDING PARTY.\n"; + print "HOWEVER, YOU HAD TO LEAVE ALL YOUR FURS BEHIND.\n"; + supplies_ny(); + return 1418; + } + elsif ($P <= 10) + { + $Beaver_price /= 2; + $Mink_price /= 2; + print "YOUR MINK AND BEAVER WERE DAMAGED ON YOUR TRIP.\n"; + print "YOU RECEIVE ONLY HALF THE CURRENT PRICE FOR THESE FURS.\n"; + supplies_ny(); + } + return 1410; +} From 405c50e0e7e1594ab9fc01dd434f1c0a077a61d9 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Wed, 10 May 2023 07:54:36 +1000 Subject: [PATCH 124/141] Fix computer targetting errors --- 77_Salvo/csharp/Coordinate.cs | 12 +-- 77_Salvo/csharp/Game.cs | 78 +++++++++++-------- 77_Salvo/csharp/Grid.cs | 6 +- 77_Salvo/csharp/Program.cs | 3 +- 77_Salvo/csharp/Resources/IHit.txt | 2 +- .../csharp/Resources/WhereAreYourShips.txt | 2 +- .../csharp/Targetting/ComputerShotSelector.cs | 5 ++ .../csharp/Targetting/HumanShotSelector.cs | 2 +- .../Targetting/SearchPatternShotSelector.cs | 5 +- 9 files changed, 67 insertions(+), 48 deletions(-) diff --git a/77_Salvo/csharp/Coordinate.cs b/77_Salvo/csharp/Coordinate.cs index 45d149c1..4cd2c433 100644 --- a/77_Salvo/csharp/Coordinate.cs +++ b/77_Salvo/csharp/Coordinate.cs @@ -2,14 +2,14 @@ namespace Salvo; internal record struct Coordinate(int Value) { - public const int MinValue = 0; - public const int MaxValue = 9; + public const int MinValue = 1; + public const int MaxValue = 10; - public static IEnumerable Range => Enumerable.Range(0, 10).Select(v => new Coordinate(v)); + public static IEnumerable Range => Enumerable.Range(1, 10).Select(v => new Coordinate(v)); public bool IsInRange => Value is >= MinValue and <= MaxValue; - public static Coordinate Create(float value) => new((int)value - 1); + public static Coordinate Create(float value) => new((int)value); public static bool TryCreateValid(float value, out Coordinate coordinate) { @@ -35,11 +35,11 @@ internal record struct Coordinate(int Value) _ => this }; - public static implicit operator Coordinate(float value) => new((int)value); + public static implicit operator Coordinate(float value) => Create(value); public static implicit operator int(Coordinate coordinate) => coordinate.Value; public static Coordinate operator +(Coordinate coordinate, int offset) => new(coordinate.Value + offset); public static int operator -(Coordinate a, Coordinate b) => a.Value - b.Value; - public override string ToString() => $" {Value + 1} "; + public override string ToString() => $" {Value} "; } diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index 616f0cbc..c9d537d5 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -56,9 +56,9 @@ L2250: goto L2890; } foreach (var shot1 in humanShotSelector.GetShots()) { - if (computerGrid.IsHit(shot1, turnNumber, out var shipName)) + if (computerGrid.IsHit(shot1, turnNumber, out var ship)) { - _io.Write(Strings.YouHit(shipName)); + _io.Write(Strings.YouHit(ship.Name)); } } L2620: if (startResponse == "YES") { goto L2670; } @@ -81,7 +81,7 @@ L2970: if (hitShipValue[i]>0) { goto L3800; } } temp = computerShotSelector.GetShots().ToArray(); // display shots -L3380: if (seeShotsResponse == "yes") +L3380: if (seeShotsResponse == "YES") { foreach (var shot in temp) { @@ -90,17 +90,17 @@ L3380: if (seeShotsResponse == "yes") } foreach (var shot in temp) { - if (!humanGrid.IsHit(shot, turnNumber, out var shipName)) + if (!humanGrid.IsHit(shot, turnNumber, out var ship)) { continue; } - _io.WriteLine(Strings.IHit(shipName)); + _io.Write(Strings.IHit(ship.Name)); L3570: for (var j = 1; j <= 12; j++) { // record hit L3580: if (hitTurnRecord[j] != -1) { continue; } L3590: hitTurnRecord[j]=10+turnNumber; -L3600: hitShipValue[j]=humanGrid[shot]; +L3600: hitShipValue[j]=ship.Value; // look for past hits on same ship L3610: var shipHits=0; L3620: for (var k = 1; k <= 12; k++) @@ -132,51 +132,65 @@ L3470: humanGrid[shot]=10+turnNumber; } L3490: goto L1950; L3800: //REM************************USINGEARRAY - var tempGrid = new Grid(); + var tempGrid = Position.All.ToDictionary(x => x, _ => 0); L3860: for (var i = 1; i <= 12; i++) { if (hitTurnRecord[i]<10) { continue; } foreach (var position in Position.All) { - if (humanGrid[position]>=10) + if (humanGrid.WasTargetedAt(position, out _)) { - foreach (var neighbour in position.Neighbours) - { - if (humanGrid[neighbour] == hitTurnRecord[i]) - { - tempGrid[position] += hitTurnRecord[i]-position.Y*(int)(hitShipValue[i]+.5F); - } - } - } - else - { tempGrid[position]=-10000000; + continue; + } + + foreach (var neighbour in position.Neighbours) + { + if (humanGrid.WasTargetedAt(neighbour, out var turn) && turn == (hitTurnRecord[i] - 10)) + { + tempGrid[position] += hitTurnRecord[i]-position.Y*(int)(hitShipValue[i]+.5F); + } } } } -L4030: for (var i = 1; i <= numberOfShots; i++) +L4030: for (var i = 0; i < numberOfShots; i++) { -L4040: temp[i]=i; +L4040: temp[i]=i+1; } foreach (var position in Position.All) { -L4090: var Q9=1; -L4100: for (var i = 1; i <= numberOfShots; i++) +L4090: var Q9=0; +L4100: for (var i = 0; i < numberOfShots; i++) { -L4110: if (tempGrid[temp[i]]>=tempGrid[temp[Q9]]) { continue; } -L4120: Q9=i; +L4110: if (tempGrid[temp[i]] < tempGrid[temp[Q9]]) + { +L4120: Q9 = i; + } } -L4131: if (position.X>numberOfShots) { goto L4140; } -L4132: if (position.IsOnDiagonal) { goto L4210; } -L4140: if (tempGrid[position] _values = + new(File.ReadAllLines("data.txt").Select(l => float.Parse(l) / 1000000)); + private float _previous; + + public float NextFloat() => _previous = _values.Dequeue(); + + public float PreviousFloat() => _previous; + + public void Reseed(int seed) + { + throw new NotImplementedException(); + } +} + diff --git a/77_Salvo/csharp/Grid.cs b/77_Salvo/csharp/Grid.cs index 85e3f387..43dd61b4 100644 --- a/77_Salvo/csharp/Grid.cs +++ b/77_Salvo/csharp/Grid.cs @@ -75,14 +75,12 @@ internal class Grid internal bool WasTargetedAt(Position position, out int turnTargeted) => _shots.TryGetValue(position, out turnTargeted); - internal bool IsHit(Position position, int turnNumber, [NotNullWhen(true)] out string? shipName) + internal bool IsHit(Position position, int turnNumber, [NotNullWhen(true)] out Ship? ship) { - shipName = null; _shots[position] = turnNumber; - var ship = _ships.FirstOrDefault(s => s.IsHit(position)); + ship = _ships.FirstOrDefault(s => s.IsHit(position)); if (ship == null) { return false; } - shipName = ship.Name; if (ship.IsDestroyed) { _ships.Remove(ship); } diff --git a/77_Salvo/csharp/Program.cs b/77_Salvo/csharp/Program.cs index 6878fd38..5dde4d05 100644 --- a/77_Salvo/csharp/Program.cs +++ b/77_Salvo/csharp/Program.cs @@ -5,4 +5,5 @@ global using Salvo; global using Salvo.Ships; global using static Salvo.Resources.Resource; -new Game(new ConsoleIO(), new RandomNumberGenerator()).Play(); +//new Game(new ConsoleIO(), new RandomNumberGenerator()).Play(); +new Game(new ConsoleIO(), new DataRandom()).Play(); diff --git a/77_Salvo/csharp/Resources/IHit.txt b/77_Salvo/csharp/Resources/IHit.txt index a3f3dccf..3b43216c 100644 --- a/77_Salvo/csharp/Resources/IHit.txt +++ b/77_Salvo/csharp/Resources/IHit.txt @@ -1 +1 @@ -I hot your {0} +I hit your {0} diff --git a/77_Salvo/csharp/Resources/WhereAreYourShips.txt b/77_Salvo/csharp/Resources/WhereAreYourShips.txt index 797c1144..e8391a46 100644 --- a/77_Salvo/csharp/Resources/WhereAreYourShips.txt +++ b/77_Salvo/csharp/Resources/WhereAreYourShips.txt @@ -1 +1 @@ -Where are your ships? +Where are your ships? \ No newline at end of file diff --git a/77_Salvo/csharp/Targetting/ComputerShotSelector.cs b/77_Salvo/csharp/Targetting/ComputerShotSelector.cs index c8f57d20..c69c36d2 100644 --- a/77_Salvo/csharp/Targetting/ComputerShotSelector.cs +++ b/77_Salvo/csharp/Targetting/ComputerShotSelector.cs @@ -10,6 +10,11 @@ internal class ComputerShotSelector : ShotSelector _displayShots = displayShots; } + internal override IEnumerable GetShots() + { + throw new NotImplementedException(); + } + private void DisplayShots(IEnumerable shots, IReadWrite io) { if (_displayShots) diff --git a/77_Salvo/csharp/Targetting/HumanShotSelector.cs b/77_Salvo/csharp/Targetting/HumanShotSelector.cs index 40812926..b4a0db13 100644 --- a/77_Salvo/csharp/Targetting/HumanShotSelector.cs +++ b/77_Salvo/csharp/Targetting/HumanShotSelector.cs @@ -12,7 +12,7 @@ internal class HumanShotSelector : ShotSelector internal override IEnumerable GetShots() { - var shots = new Position[NumberOfShots()]; + var shots = new Position[NumberOfShots]; for (var i = 0; i < shots.Length; i++) { diff --git a/77_Salvo/csharp/Targetting/SearchPatternShotSelector.cs b/77_Salvo/csharp/Targetting/SearchPatternShotSelector.cs index 4e3fcafb..329a0a98 100644 --- a/77_Salvo/csharp/Targetting/SearchPatternShotSelector.cs +++ b/77_Salvo/csharp/Targetting/SearchPatternShotSelector.cs @@ -15,6 +15,7 @@ internal class SearchPatternShotSelector : ShotSelector internal override IEnumerable GetShots() { + _shots.Clear(); while(_shots.Count < NumberOfShots) { var (seed, _) = _random.NextShipPosition(); @@ -31,11 +32,11 @@ internal class SearchPatternShotSelector : ShotSelector _searchPattern.Reset(); if (attemptsLeft-- == 0) { return; } candidateShot = candidateShot.BringIntoRange(_random); - if (FindValidShots(candidateShot)) { return; } + if (FindValidShots(ref candidateShot)) { return; } } } - private bool FindValidShots(Position candidateShot) + private bool FindValidShots(ref Position candidateShot) { while (true) { From 698eff07585d7a227783e875a6d3276fff063f9b Mon Sep 17 00:00:00 2001 From: kbrannen Date: Mon, 15 May 2023 01:11:36 -0500 Subject: [PATCH 125/141] added 96-Target for perl --- 86_Target/perl/target.pl | 107 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 86_Target/perl/target.pl diff --git a/86_Target/perl/target.pl b/86_Target/perl/target.pl new file mode 100644 index 00000000..cb552a39 --- /dev/null +++ b/86_Target/perl/target.pl @@ -0,0 +1,107 @@ +#!/usr/bin/perl + +# Target program in Perl +# Modified so that if the user enters "quit" or "stop" for the input, the program will exit. +# Values can be space and/or comma separated. +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# globals +my $R = 1; +my $R1 = 57.296; +my $Pi = 3.14159; + +print "\n"; +print " " x 33, "TARGET\n"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; + +print "YOU ARE THE WEAPONS OFFICER ON THE STARSHIP ENTERPRISE\n"; +print "AND THIS IS A TEST TO SEE HOW ACCURATE A SHOT YOU\n"; +print "ARE IN A THREE-DIMENSIONAL RANGE. YOU WILL BE TOLD\n"; +print "THE RADIAN OFFSET FOR THE X AND Z AXES, THE LOCATION\n"; +print "OF THE TARGET IN THREE DIMENSIONAL RECTANGULAR COORDINATES,\n"; +print "THE APPROXIMATE NUMBER OF DEGREES FROM THE X AND Z\n"; +print "AXES, AND THE APPROXIMATE DISTANCE TO THE TARGET.\n"; +print "YOU WILL THEN PROCEEED TO SHOOT AT THE TARGET UNTIL IT IS\n"; +print "DESTROYED!\n\n"; +print "GOOD LUCK!!\n\n"; + +while (1) +{ + my $A = rand(1) * 2 * $Pi; + my $B = rand(1) * 2 * $Pi; + my $P1 = 100000 * rand(1) + rand(1); + my $X = sin($B) * cos($A) * $P1; + my $Y = sin($B) * sin($A) * $P1; + my $Z = cos($B) * $P1; + print "RADIANS FROM X AXIS = $A FROM Z AXIS = $B\n"; + print "TARGET SIGHTED: APPROXIMATE COORDINATES: X=$X Y=$Y Z=$Z\n"; + + while (1) + { + my $P3; + $R++; + + if ($R == 1) { $P3 = int($P1 * .05) * 20; } + elsif ($R == 2) { $P3 = int($P1 * .1) * 10; } + elsif ($R == 3) { $P3 = int($P1 * .5) * 2; } + elsif ($R == 4) { $P3 = int($P1); } + else { $P3 = $P1; } + + print " ESTIMATED DISTANCE: $P3\n\n"; + print "INPUT ANGLE DEVIATION FROM X, DEVIATION FROM Z, DISTANCE: "; + chomp(my $ans = lc(<>)); + exit(0) if ($ans eq "quit" || $ans eq "stop"); + + my ($A1, $B1, $P2) = split(/[,\s]+/, $ans); + print "\n"; + + if ($P2 >= 20) + { + $A1 /= $R1; + $B1 /= $R1; + print "RADIANS FROM X AXIS = $A1 FROM Z AXIS = $B1\n"; + my $X1 = $P2 * sin($B1) * cos($A1); + my $Y1 = $P2 * sin($B1) * sin($A1); + my $Z1 = $P2 * cos($B1); + my $D = (($X1 - $X) ** 2 + ($Y1 - $Y) ** 2 + ($Z1 - $Z) ** 2) ** (0.5); + + if ($D <= 20) + { + print "\n * * * HIT * * * TARGET IS NON-FUNCTIONAL\n"; + print "\nDISTANCE OF EXPLOSION FROM TARGET WAS $D KILOMETERS.\n"; + print "\nMISSION ACCOMPLISHED IN $R SHOTS.\n"; + last; + } + else + { + my $X2 = $X1 - $X; + my $Y2 = $Y1 - $Y; + my $Z2 = $Z1 - $Z; + + if ($X2 < 0) { print "SHOT BEHIND TARGET ", -$X2, " KILOMETERS.\n"; } + else { print "SHOT IN FRONT OF TARGET $X2 KILOMETERS.\n"; } + + if ($Y2 < 0) { print "SHOT TO RIGHT OF TARGET ", -$Y2, " KILOMETERS.\n"; } + else { print "SHOT TO LEFT OF TARGET $Y2 KILOMETERS.\n"; } + + if ($Z2 < 0) { print "SHOT BELOW TARGET ", -$Z2, " KILOMETERS.\n"; } + else { print "SHOT ABOVE TARGET $Z2 KILOMETERS.\n"; } + + print "APPROX POSITION OF EXPLOSION: X=$X1 Y=$Y1 Z=$Z1\n"; + print " DISTANCE FROM TARGET = $D\n\n\n"; + next; + } + } + else + { + print "YOU BLEW YOURSELF UP!!\n"; + last; + } + } + + $R = 0; + print "\n\n\n\n\nNEXT TARGET...\n\n"; +} From 1e5a0454998fedd10d7583937643974f3d2ddbf6 Mon Sep 17 00:00:00 2001 From: kbrannen Date: Mon, 15 May 2023 01:16:01 -0500 Subject: [PATCH 126/141] updated README --- 86_Target/perl/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/86_Target/perl/README.md b/86_Target/perl/README.md index e69c8b81..eca6b9fa 100644 --- a/86_Target/perl/README.md +++ b/86_Target/perl/README.md @@ -1,3 +1,9 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) Conversion to [Perl](https://www.perl.org/) + +Modified so that if the user enters "quit" or "stop" for the input, the program will exit. +This way the user doesn't have to enter Contorl-C to quit. + +Target values can be space and/or comma separated, so "1 2 3" is valid, as is "1,2,3" or even "1, 2, 3". +I believe the original Basic program wanted "1,2,3" or else each on a separate line. From abf052017e6e808c2c260c680ce0613a99fc2866 Mon Sep 17 00:00:00 2001 From: kbrannen Date: Mon, 15 May 2023 12:16:12 -0500 Subject: [PATCH 127/141] added 42-gunner for perl --- 42_Gunner/perl/gunner.pl | 81 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100755 42_Gunner/perl/gunner.pl diff --git a/42_Gunner/perl/gunner.pl b/42_Gunner/perl/gunner.pl new file mode 100755 index 00000000..9df20deb --- /dev/null +++ b/42_Gunner/perl/gunner.pl @@ -0,0 +1,81 @@ +#!/usr/bin/perl + +# Gunner program in Perl +# Required extensive restructuring to remove all of the GOTO's. +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# globals +my $Max_range = int(40000*rand(1)+20000); +my $Total_shots = 0; +my $Games = 0; + +print "\n"; +print " " x 30, "GUNNER\n"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; + +print "YOU ARE THE OFFICER-IN-CHARGE, GIVING ORDERS TO A GUN\n"; +print "CREW, TELLING THEM THE DEGREES OF ELEVATION YOU ESTIMATE\n"; +print "WILL PLACE A PROJECTILE ON TARGET. A HIT WITHIN 100 YARDS\n"; +print "OF THE TARGET WILL DESTROY IT.\n\n"; +print "MAXIMUM RANGE OF YOUR GUN IS $Max_range YARDS.\n\n"; + +GAME: while (1) +{ + my $target_dist = int($Max_range * (.1 + .8 * rand(1))); + my $shots = 0; + print "DISTANCE TO THE TARGET IS $target_dist YARDS.\n\n"; + while (1) + { + my $elevation = get_elevation(); # in degrees + $shots++; + my $dist = int($target_dist - ($Max_range * sin(2 * $elevation / 57.3))); + if (abs($dist) < 100) + { + print "*** TARGET DESTROYED *** $shots ROUNDS OF AMMUNITION EXPENDED.\n"; + $Total_shots += $shots; + if ($Games++ == 4) + { + print "\n\nTOTAL ROUNDS EXPENDED WERE: $Total_shots\n"; + if ($Total_shots > 18) { print "BETTER GO BACK TO FORT SILL FOR REFRESHER TRAINING!\n"; } + else { print "NICE SHOOTING !!\n"; } + last; + } + print "\nTHE FORWARD OBSERVER HAS SIGHTED MORE ENEMY ACTIVITY...\n"; + next GAME; + } + if ($dist > 100) { print "SHORT OF TARGET BY ", abs($dist)," YARDS.\n"; } + else { print "OVER TARGET BY ", abs($dist), " YARDS.\n"; } + + if ($shots >= 5) + { + print "\nBOOM !!!! YOU HAVE JUST BEEN DESTROYED BY THE ENEMY.\n\n\n\n"; + print "BETTER GO BACK TO FORT SILL FOR REFRESHER TRAINING!\n"; + last; + } + } + + print "\nTRY AGAIN (Y OR N): "; + chomp(my $ans=uc(<>)); + if ($ans ne "Y") { last; } + else { $Games = 0; $Total_shots = 0; } +} + +print "\nOK. RETURN TO BASE CAMP.\n"; + +#################################### + +sub get_elevation +{ + my $elevation; + while (1) + { + print "\nELEVATION: "; + chomp($elevation = <>); + if ($elevation > 89) { print "MAXIMUM ELEVATION IS 89 DEGREES.\n"; } + elsif ($elevation < 1) { print "MINIMUM ELEVATION IS ONE DEGREE.\n"; } + else { return $elevation; } + } +} From 6e01ee5b6a3258c5abb244433176ea0e8b98ab0c Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Tue, 16 May 2023 08:05:39 +1000 Subject: [PATCH 128/141] Simplify hit recording --- 77_Salvo/csharp/Game.cs | 40 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index c9d537d5..978701cb 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -95,42 +95,28 @@ L3380: if (seeShotsResponse == "YES") continue; } _io.Write(Strings.IHit(ship.Name)); -L3570: for (var j = 1; j <= 12; j++) + for (var j = 1; j <= 12; j++) { - // record hit -L3580: if (hitTurnRecord[j] != -1) { continue; } -L3590: hitTurnRecord[j]=10+turnNumber; -L3600: hitShipValue[j]=ship.Value; - // look for past hits on same ship -L3610: var shipHits=0; -L3620: for (var k = 1; k <= 12; k++) + if (hitTurnRecord[j] == -1) { -L3630: if (hitShipValue[k] != hitShipValue[j]) { continue; } -L3640: shipHits++; + hitTurnRecord[j]=10+turnNumber; + hitShipValue[j]=ship.Value; + break; } - // if ship is not sunk -L3660: if (shipHits != (int)(hitShipValue[j]+.5F)+1+(int)(hitShipValue[j]+.5F)/3) { goto L3470; } - // otherwise, remove ship hit records -L3670: for (var k = 1; k <= 12; k++) + } + if (ship.IsDestroyed) + { + for (var k = 1; k <= 12; k++) { -L3680: if (hitShipValue[k] == hitShipValue[j]) + if (hitShipValue[k] == ship.Value) { -L3700: hitShipValue[k] = hitTurnRecord[k] = -1; + hitShipValue[k] = hitTurnRecord[k] = -1; } } -L3720: goto L3470; } - // we shouldn't get here -L3740: _io.WriteLine("PROGRAM ABORT:"); -L3750: for (var j = 1; j <= 12; j++) - { -L3760: _io.WriteLine($"{nameof(hitTurnRecord)}( {j} ) = {hitTurnRecord[j]}"); -L3770: _io.WriteLine($"{nameof(hitShipValue)}( {j} ) = {hitShipValue[j]}"); - } - return; -L3470: humanGrid[shot]=10+turnNumber; + humanGrid[shot]=10+turnNumber; } -L3490: goto L1950; + goto L1950; L3800: //REM************************USINGEARRAY var tempGrid = Position.All.ToDictionary(x => x, _ => 0); L3860: for (var i = 1; i <= 12; i++) From 30360f278446d0ab2e7f99ec3216fe8a3d3d0d4e Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Tue, 16 May 2023 17:45:48 +1000 Subject: [PATCH 129/141] Improve computer shot tracking/selection --- 77_Salvo/csharp/Game.cs | 43 +++++++---------------------- 77_Salvo/csharp/Grid.cs | 12 -------- 77_Salvo/csharp/Ships/Battleship.cs | 1 - 77_Salvo/csharp/Ships/Cruiser.cs | 1 - 77_Salvo/csharp/Ships/Destroyer.cs | 1 - 77_Salvo/csharp/Ships/Ship.cs | 3 +- 6 files changed, 11 insertions(+), 50 deletions(-) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index 978701cb..33d750df 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -17,15 +17,9 @@ internal class Game { _io.Write(Streams.Title); - var hitTurnRecord = new int[13]; + var hitRecords = new List<(int Turn, Ship Ship)>(); var temp = new Position[13]; - var hitShipValue = new float[13]; - for (var i = 1; i <= 12; i++) - { - hitTurnRecord[i] = -1; - hitShipValue[i] = -1; - } var computerGrid = new Grid(_random); var humanGrid = new Grid(_io); var humanShotSelector = new HumanShotSelector(humanGrid, computerGrid, _io); @@ -74,11 +68,7 @@ L2880: if (numberOfShots != 0) { goto L2960; } L2890: _io.Write(Streams.YouWon); L2900: return; -L2960: for (var i = 1; i <= 12; i++) - { - // if damaged ships -L2970: if (hitShipValue[i]>0) { goto L3800; } - } +L2960: if (humanGrid.Ships.Any(s => s.IsDamaged)) { goto L3800; } temp = computerShotSelector.GetShots().ToArray(); // display shots L3380: if (seeShotsResponse == "YES") @@ -95,33 +85,20 @@ L3380: if (seeShotsResponse == "YES") continue; } _io.Write(Strings.IHit(ship.Name)); - for (var j = 1; j <= 12; j++) - { - if (hitTurnRecord[j] == -1) - { - hitTurnRecord[j]=10+turnNumber; - hitShipValue[j]=ship.Value; - break; - } - } if (ship.IsDestroyed) { - for (var k = 1; k <= 12; k++) - { - if (hitShipValue[k] == ship.Value) - { - hitShipValue[k] = hitTurnRecord[k] = -1; - } - } + hitRecords = hitRecords.Where(hr => hr.Ship != ship).ToList(); + } + else + { + hitRecords.Add((turnNumber, ship)); } - humanGrid[shot]=10+turnNumber; } goto L1950; L3800: //REM************************USINGEARRAY var tempGrid = Position.All.ToDictionary(x => x, _ => 0); -L3860: for (var i = 1; i <= 12; i++) +L3860: foreach (var (hitTurn, ship) in hitRecords) { - if (hitTurnRecord[i]<10) { continue; } foreach (var position in Position.All) { if (humanGrid.WasTargetedAt(position, out _)) @@ -132,9 +109,9 @@ L3860: for (var i = 1; i <= 12; i++) foreach (var neighbour in position.Neighbours) { - if (humanGrid.WasTargetedAt(neighbour, out var turn) && turn == (hitTurnRecord[i] - 10)) + if (humanGrid.WasTargetedAt(neighbour, out var turn) && turn == hitTurn) { - tempGrid[position] += hitTurnRecord[i]-position.Y*(int)(hitShipValue[i]+.5F); + tempGrid[position] += hitTurn + 10 - position.Y * ship.Shots; } } } diff --git a/77_Salvo/csharp/Grid.cs b/77_Salvo/csharp/Grid.cs index 43dd61b4..0c84d688 100644 --- a/77_Salvo/csharp/Grid.cs +++ b/77_Salvo/csharp/Grid.cs @@ -57,18 +57,6 @@ internal class Grid } } - public float this[Position position] - { - get => _shots.TryGetValue(position, out var value) - ? value + 10 - : _ships.FirstOrDefault(s => s.Positions.Contains(position))?.Value ?? 0; - set - { - _ = _ships.FirstOrDefault(s => s.IsHit(position)); - _shots[position] = (int)value - 10; - } - } - internal int UntriedSquareCount => 100 - _shots.Count; internal IEnumerable Ships => _ships.AsEnumerable(); diff --git a/77_Salvo/csharp/Ships/Battleship.cs b/77_Salvo/csharp/Ships/Battleship.cs index 1e57e59d..4f864eb6 100644 --- a/77_Salvo/csharp/Ships/Battleship.cs +++ b/77_Salvo/csharp/Ships/Battleship.cs @@ -14,5 +14,4 @@ internal sealed class Battleship : Ship internal override int Shots => 3; internal override int Size => 5; - internal override float Value => 3; } diff --git a/77_Salvo/csharp/Ships/Cruiser.cs b/77_Salvo/csharp/Ships/Cruiser.cs index 33f30e38..d004f24c 100644 --- a/77_Salvo/csharp/Ships/Cruiser.cs +++ b/77_Salvo/csharp/Ships/Cruiser.cs @@ -14,5 +14,4 @@ internal sealed class Cruiser : Ship internal override int Shots => 2; internal override int Size => 3; - internal override float Value => 2; } diff --git a/77_Salvo/csharp/Ships/Destroyer.cs b/77_Salvo/csharp/Ships/Destroyer.cs index 0e2a023b..6523395d 100644 --- a/77_Salvo/csharp/Ships/Destroyer.cs +++ b/77_Salvo/csharp/Ships/Destroyer.cs @@ -14,5 +14,4 @@ internal sealed class Destroyer : Ship internal override int Shots => 1; internal override int Size => 2; - internal override float Value => Name.EndsWith("") ? 1 : 0.5F; } diff --git a/77_Salvo/csharp/Ships/Ship.cs b/77_Salvo/csharp/Ships/Ship.cs index 58f311c2..ee204d3f 100644 --- a/77_Salvo/csharp/Ships/Ship.cs +++ b/77_Salvo/csharp/Ships/Ship.cs @@ -24,8 +24,7 @@ internal abstract class Ship internal string Name { get; } internal abstract int Shots { get; } internal abstract int Size { get; } - internal abstract float Value { get; } - internal IEnumerable Positions => _positions; + internal bool IsDamaged => _positions.Count > 0 && _positions.Count < Size; internal bool IsDestroyed => _positions.Count == 0; internal bool IsHit(Position position) => _positions.Remove(position); From e27b5ec6e5018a051e9c1a85c91fd83875da8817 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Thu, 18 May 2023 19:27:56 +1000 Subject: [PATCH 130/141] Move shot selection into ComputerShotSelector --- 77_Salvo/csharp/Game.cs | 66 ++--------------- .../csharp/Targetting/ComputerShotSelector.cs | 21 ++---- .../KnownHitsShotSelectionStrategy.cs | 71 +++++++++++++++++++ .../Targetting/ShotSelectionStrategy.cs | 16 +++++ 4 files changed, 101 insertions(+), 73 deletions(-) create mode 100644 77_Salvo/csharp/Targetting/KnownHitsShotSelectionStrategy.cs create mode 100644 77_Salvo/csharp/Targetting/ShotSelectionStrategy.cs diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index 33d750df..95ccef23 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -17,13 +17,13 @@ internal class Game { _io.Write(Streams.Title); - var hitRecords = new List<(int Turn, Ship Ship)>(); + var damagedShips = new List<(int Turn, Ship Ship)>(); var temp = new Position[13]; var computerGrid = new Grid(_random); var humanGrid = new Grid(_io); var humanShotSelector = new HumanShotSelector(humanGrid, computerGrid, _io); - var computerShotSelector = new SearchPatternShotSelector(computerGrid, humanGrid, _random); + var computerShotSelector = new ComputerShotSelector(computerGrid, humanGrid, _random); var startResponse = _io.ReadString(Prompts.Start); while (startResponse == Strings.WhereAreYourShips) { @@ -64,12 +64,11 @@ L2850: if (humanGrid.UntriedSquareCount > numberOfShots) { goto L2880; } L2860: _io.Write(Streams.IHaveMoreShotsThanSquares); L2270: _io.Write(Streams.IWon); return; -L2880: if (numberOfShots != 0) { goto L2960; } +L2880: if (numberOfShots == 0) { goto L2960; } L2890: _io.Write(Streams.YouWon); L2900: return; -L2960: if (humanGrid.Ships.Any(s => s.IsDamaged)) { goto L3800; } - temp = computerShotSelector.GetShots().ToArray(); +L2960: temp = computerShotSelector.GetShots().ToArray(); // display shots L3380: if (seeShotsResponse == "YES") { @@ -80,64 +79,13 @@ L3380: if (seeShotsResponse == "YES") } foreach (var shot in temp) { - if (!humanGrid.IsHit(shot, turnNumber, out var ship)) + if (humanGrid.IsHit(shot, turnNumber, out var ship)) { - continue; - } - _io.Write(Strings.IHit(ship.Name)); - if (ship.IsDestroyed) - { - hitRecords = hitRecords.Where(hr => hr.Ship != ship).ToList(); - } - else - { - hitRecords.Add((turnNumber, ship)); + _io.Write(Strings.IHit(ship.Name)); + computerShotSelector.RecordHit(ship, turnNumber); } } goto L1950; -L3800: //REM************************USINGEARRAY - var tempGrid = Position.All.ToDictionary(x => x, _ => 0); -L3860: foreach (var (hitTurn, ship) in hitRecords) - { - foreach (var position in Position.All) - { - if (humanGrid.WasTargetedAt(position, out _)) - { - tempGrid[position]=-10000000; - continue; - } - - foreach (var neighbour in position.Neighbours) - { - if (humanGrid.WasTargetedAt(neighbour, out var turn) && turn == hitTurn) - { - tempGrid[position] += hitTurn + 10 - position.Y * ship.Shots; - } - } - } - } -L4030: for (var i = 0; i < numberOfShots; i++) - { -L4040: temp[i]=i+1; - } - foreach (var position in Position.All) - { -L4090: var Q9=0; -L4100: for (var i = 0; i < numberOfShots; i++) - { -L4110: if (tempGrid[temp[i]] < tempGrid[temp[Q9]]) - { -L4120: Q9 = i; - } - } -L4131: if (position.X <= numberOfShots && position.IsOnDiagonal) { continue; } -L4140: if (tempGrid[position] GetShots() { - throw new NotImplementedException(); + return _knownHitsStrategy.GetShots(NumberOfShots) ?? _searchPatternShotSelector.GetShots(); } - private void DisplayShots(IEnumerable shots, IReadWrite io) - { - if (_displayShots) - { - foreach (var shot in shots) - { - io.WriteLine(shot); - } - } - } + internal void RecordHit(Ship ship, int turn) => _knownHitsStrategy.RecordHit(ship, turn); } diff --git a/77_Salvo/csharp/Targetting/KnownHitsShotSelectionStrategy.cs b/77_Salvo/csharp/Targetting/KnownHitsShotSelectionStrategy.cs new file mode 100644 index 00000000..917b232e --- /dev/null +++ b/77_Salvo/csharp/Targetting/KnownHitsShotSelectionStrategy.cs @@ -0,0 +1,71 @@ +namespace Salvo.Targetting; + +internal class KnownHitsShotSelectionStrategy : ShotSelectionStrategy +{ + private readonly List<(int Turn, Ship Ship)> _damagedShips = new(); + + internal KnownHitsShotSelectionStrategy(Grid target) + : base(target) + { + } + + internal IEnumerable? GetShots(int numberOfShots) + { + if (!_damagedShips.Any()) { return null; } + + var tempGrid = Position.All.ToDictionary(x => x, _ => 0); + var shots = Enumerable.Range(1, numberOfShots).Select(x => new Position(x, x)).ToArray(); + + foreach (var (hitTurn, ship) in _damagedShips) + { + foreach (var position in Position.All) + { + if (WasSelectedPreviously(position)) + { + tempGrid[position]=-10000000; + continue; + } + + foreach (var neighbour in position.Neighbours) + { + if (WasSelectedPreviously(neighbour, out var turn) && turn == hitTurn) + { + tempGrid[position] += hitTurn + 10 - position.Y * ship.Shots; + } + } + } + } + + foreach (var position in Position.All) + { + var Q9=0; + for (var i = 0; i < numberOfShots; i++) + { + if (tempGrid[shots[i]] < tempGrid[shots[Q9]]) + { + Q9 = i; + } + } + if (position.X <= numberOfShots && position.IsOnDiagonal) { continue; } + if (tempGrid[position] x.Ship == ship); + } + else + { + _damagedShips.Add((turn, ship)); + } + } +} diff --git a/77_Salvo/csharp/Targetting/ShotSelectionStrategy.cs b/77_Salvo/csharp/Targetting/ShotSelectionStrategy.cs new file mode 100644 index 00000000..f98c7b4f --- /dev/null +++ b/77_Salvo/csharp/Targetting/ShotSelectionStrategy.cs @@ -0,0 +1,16 @@ +namespace Salvo.Targetting; + +internal abstract class ShotSelectionStrategy +{ + private readonly Grid _target; + protected ShotSelectionStrategy(Grid target) + { + _target = target; + } + + protected bool WasSelectedPreviously(Position position) + => _target.WasTargetedAt(position, out _); + + protected bool WasSelectedPreviously(Position position, out int turn) + => _target.WasTargetedAt(position, out turn); +} From 46f98ca01d565fdd2675694c418833e2bf4acf59 Mon Sep 17 00:00:00 2001 From: kbrannen Date: Fri, 19 May 2023 02:11:02 -0500 Subject: [PATCH 131/141] added 56-life2 for perl added a perl version for the game; fixed a bug in the basic code; updated the game's README; updated perl's README. --- 56_Life_for_Two/README.md | 14 ++ 56_Life_for_Two/lifefortwo.bas | 4 +- 56_Life_for_Two/perl/README.md | 18 +++ 56_Life_for_Two/perl/lifefortwo.pl | 215 +++++++++++++++++++++++++++++ 4 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 56_Life_for_Two/perl/lifefortwo.pl diff --git a/56_Life_for_Two/README.md b/56_Life_for_Two/README.md index 3d906342..e68c8d64 100644 --- a/56_Life_for_Two/README.md +++ b/56_Life_for_Two/README.md @@ -48,3 +48,17 @@ http://www.vintage-basic.net/games.html #### Porting Notes (please note any difficulties or challenges in porting here) + +Note: The original program has a bug. The instructions say that if both players +enter the same cell that the cell is set to 0 or empty. However, the original +Basic program tells the player "ILLEGAL COORDINATES" and makes another cell be entered, +giving a slightly unfair advantage to the 2nd player. + +The Perl verson of the program fixes the bug and follows the instructions. + +Note: The original code had "GOTO 800" but label 800 didn't exist; it should have gone to label 999. +The Basic program has been fixed. + +Note: The Basic program is written to assume it's being played on a Teletype, i.e. output is printed +on paper. To play on a terminal the input must not be echoed, which can be a challenge to do portably +and without tying the solution to a specific OS. Some versions may tell you how to do this, others might not. diff --git a/56_Life_for_Two/lifefortwo.bas b/56_Life_for_Two/lifefortwo.bas index e970ef6d..4faf1f12 100644 --- a/56_Life_for_Two/lifefortwo.bas +++ b/56_Life_for_Two/lifefortwo.bas @@ -60,8 +60,8 @@ 571 IF M3=0 THEN B=1: GOTO 575 572 IF M2=0 THEN B=2: GOTO 575 573 GOTO 580 -574 PRINT: PRINT "A DRAW":GOTO 800 -575 PRINT: PRINT "PLAYER";B;"IS THE WINNER":GOTO 800 +574 PRINT: PRINT "A DRAW":GOTO 999 +575 PRINT: PRINT "PLAYER";B;"IS THE WINNER":GOTO 999 580 FOR B=1 TO 2: PRINT: PRINT: PRINT "PLAYER";B;: GOSUB 700 581 IF B=99 THEN 560 582 NEXT B diff --git a/56_Life_for_Two/perl/README.md b/56_Life_for_Two/perl/README.md index e69c8b81..7b3b41a3 100644 --- a/56_Life_for_Two/perl/README.md +++ b/56_Life_for_Two/perl/README.md @@ -1,3 +1,21 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) Conversion to [Perl](https://www.perl.org/) + +Note: The original program has a bug (see the README in the above dir). This Perl version fixes it. + +Note: For input, the X value is to the right while the Y value is down. +Therefore, the top right cell is "5,1", not "1,5". + +The original program was made to be played on a Teletype, i.e. a printer on paper. +That allowed the program to "black out" the input line to hide a user's input from his/her +opponent, assuming the opponent was at least looking away. To do the equivalent on a +terminal would require a Perl module that isn't installed by default (i.e. it is not +part of CORE and would also require a C compiler to install), nor do I want to issue a +shell command to "stty" to hide the input because that would restrict the game to Linux/Unix. +This means it would have to be played on the honor system. + +However, if you want to try it, install the module "Term::ReadKey" ("sudo cpan -i Term::ReadKey" +if on Linux/Unix and you have root access). If the code finds that module, it will automatically +use it and hide the input ... and restore echoing input again when the games ends. If the module +is not found, input will be visible. diff --git a/56_Life_for_Two/perl/lifefortwo.pl b/56_Life_for_Two/perl/lifefortwo.pl new file mode 100644 index 00000000..0c50cdef --- /dev/null +++ b/56_Life_for_Two/perl/lifefortwo.pl @@ -0,0 +1,215 @@ +#!/usr/bin/perl + +# Life_For_Two program in Perl +# Required extensive restructuring to remove all of the GOTO's. +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# try to load module to hide input, set RKey to true if found +my $Rkey = eval { require Term::ReadKey } // 0; +END { Term::ReadKey::ReadMode('normal') if ($Rkey); } + +# globals +my @Board; # 2D board +my @X; # ? +my @Y; # ? +my $Player; # 1 or 2 +my $M2 = 0; # ? +my $M3 = 0; # ? + +# add 0 on front to make data 1 based +my @K = (0,3,102,103,120,130,121,112,111,12,21,30,1020,1030,1011,1021,1003,1002,1012); +my @A = (0,-1,0,1,0,0,-1,0,1,-1,-1,1,-1,-1,1,1,1); + +print "\n"; +print " " x 33, "LIFE2\n"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; +print " " x 10, "U.B. LIFE GAME\n"; +for my $j (1 .. 5) { for my $k (1 .. 5) { $Board[$j][$k] = 0; } } + +for (1 .. 2) +{ + $Player = $_; # if we make $Player the loop var, the global isn't set + my $p1 = ($Player == 2) ? 30 : 3; + print "\nPLAYER $Player - 3 LIVE PIECES.\n"; + for (1 .. 3) + { + get_input(); + $Board[$X[$Player]][$Y[$Player]] = $p1 if ($Player != 99); + } +} +print_board(); # print board after initial input + +while (1) +{ + print "\n"; + calc_board(); # calc new positions + print_board(); # print current board after calc + + if ($M2 == 0 && $M3 == 0) + { + print "\nA DRAW\n"; + last; + } + if ($M3 == 0) + { + win(1); + last; + } + if ($M2 == 0) + { + win(2); + last; + } + + for (1 .. 2) + { + $Player = $_; # if we make $Player the loop var, the global isn't set + print "\n\nPLAYER $Player "; + get_input(); + last if ($Player == 99); + } + next if ($Player == 99); + + $Board[$X[1]][$Y[1]] = 100; + $Board[$X[2]][$Y[2]] = 1000; +} +exit(0); + +########################################################### + +sub win +{ + my $p = shift; + print "\nPLAYER $p IS THE WINNER\n"; +} + +sub calc_board +{ + for my $j (1 .. 5) + { + for my $k (1 .. 5) + { + if ($Board[$j][$k] > 99) + { + $Player = $Board[$j][$k] > 999 ? 10 : 1; + for (my $c = 1 ; $c <= 15 ; $c += 2) + { + $Board[$j+$A[$c]][$k+$A[$c+1]] = ($Board[$j+$A[$c]][$k+$A[$c+1]] // 0) + $Player; + } + } + } + } +} + +sub print_board +{ + $M2 = 0; + $M3 = 0; + for my $j (0 .. 6) + { + print "\n"; + for my $k (0 .. 6) + { + if ($j != 0 && $j != 6) + { + if ($k != 0 && $k != 6) + { + print_row($j, $k); + next; + } + if ($j == 6) + { + print "0\n"; + return; + } + print " $j "; + } + else + { + if ($k == 6) + { + print " 0 "; + last; + } + print " $k "; + } + } + } +} + +sub print_row +{ + my ($j, $k) = @_; + + if ($Board[$j][$k] >= 3) + { + my $c; + for $c (1 .. 18) + { + if ($Board[$j][$k] == $K[$c]) + { + if ($c <= 9) + { + $Board[$j][$k] = 100; + $M2++; + print " * "; + } + else + { + $Board[$j][$k] = 1000; + $M3++; + print " # "; + } + return; + } + } + } + $Board[$j][$k] = 0; + print " "; +} + +sub get_input +{ + while (1) + { + print "X,Y\n"; + my $ans; + + if ($Rkey) + { + # code to hide input + Term::ReadKey::ReadMode('noecho'); + $ans = Term::ReadKey::ReadLine(0); + Term::ReadKey::ReadMode('restore'); + print "\n"; # do this since the one entered was hidden + } + else + { + # normal, input visible + chomp($ans = <>); + } + + ($Y[$Player], $X[$Player]) = split(/[,\s]+/, $ans, 2); + if ($X[$Player] > 5 || $X[$Player] < 1 || $Y[$Player] > 5 || $Y[$Player] < 1) + { + print "ILLEGAL COORDS. RETYPE\n"; + next; + } + # this tells you the cell was already taken not zero it out, bug! + #if ($Board[$X[$Player]][$Y[$Player]] != 0) + #{ + # print "ILLEGAL COORDS. RETYPE\n"; + # next; + #} + last; + } + + return if ($Player == 1 || $X[1] != $X[2] || $Y[1] != $Y[2]); + + print "SAME COORD. SET TO 0\n"; + $Board[$X[$Player]+1][$Y[$Player]+1] = 0; + $Player = 99; +} From 728e6ebaf8f1d242ac9e38dd96d22029904cc781 Mon Sep 17 00:00:00 2001 From: kbrannen Date: Sat, 20 May 2023 00:16:22 -0500 Subject: [PATCH 132/141] added 28-combat for perl --- 28_Combat/perl/combat.pl | 202 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100755 28_Combat/perl/combat.pl diff --git a/28_Combat/perl/combat.pl b/28_Combat/perl/combat.pl new file mode 100755 index 00000000..fc44a0e4 --- /dev/null +++ b/28_Combat/perl/combat.pl @@ -0,0 +1,202 @@ +#!/usr/bin/perl + +# Combat program in Perl +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# globals +my $User_army; +my $User_navy; +my $User_AF; +my $Comp_army = 30000; +my $Comp_navy = 20000; +my $Comp_AF = 22000; +my $Attack_type; +my $Attack_num; + +print "\n"; +print " " x 33, "COMBAT\n"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; + +print "I AM AT WAR WITH YOU.\nWE HAVE 72000 SOLDIERS APIECE.\n\n"; + +do { + print "DISTRIBUTE YOUR FORCES.\n"; + print "\tME\t YOU\n"; + print "ARMY\t$Comp_army\t"; + chomp($User_army = <>); + print "NAVY\t$Comp_navy\t"; + chomp($User_navy = <>); + print "A. F.\t$Comp_AF\t"; + chomp($User_AF = <>); +} while ($User_army + $User_navy + $User_AF > 72000); + +do { + print "YOU ATTACK FIRST. TYPE (1) FOR ARMY; (2) FOR NAVY;\n"; + print "AND (3) FOR AIR FORCE.\n"; + chomp($Attack_num = <>); +} while ($Attack_type < 1 && $Attack_type > 3); +do { + print "HOW MANY MEN\n"; + chomp($Attack_type = <>); +} while ($Attack_type < 0 + || ($Attack_num == 1 && $Attack_type > $User_army) + || ($Attack_num == 2 && $Attack_type > $User_navy) + || ($Attack_num == 3 && $Attack_type > $User_AF)); + +if ($Attack_num == 1) +{ + if ($Attack_type<$User_army/3) + { + print "YOU LOST $Attack_type MEN FROM YOUR ARMY.\n"; + $User_army = int($User_army-$Attack_type); + } + if ($Attack_type<2*$User_army/3) + { + print "YOU LOST ", int($Attack_type/3), " MEN, BUT I LOST ", int(2*$Comp_army/3), "\n"; + $User_army = int($User_army-$Attack_type/3); + $Comp_army = int(2*$Comp_army/3); + } + else + { + s270(); + } +} +elsif ($Attack_num == 2) +{ + if ($Attack_type < $Comp_navy/3) + { + print "YOUR ATTACK WAS STOPPED!\n"; + $User_navy = int($User_navy-$Attack_type); + } + if ($Attack_type < 2*$Comp_navy/3) + { + print "YOU DESTROYED ", int(2*$Comp_navy/3), " OF MY ARMY.\n"; + $Comp_navy = int(2*$Comp_navy/3); + } + else + { + s270(); + } +} +else # $Attack_num == 3 +{ + if ($Attack_type < $User_AF/3) + { + print "YOUR ATTACK WAS WIPED OUT.\n"; + $User_AF = int($User_AF-$Attack_type); + } + if ($Attack_type < 2*$User_AF/3) + { + print "WE HAD A DOGFIGHT. YOU WON - AND FINISHED YOUR MISSION.\n"; + $Comp_army = int(2*$Comp_army/3); + $Comp_navy = int($Comp_navy/3); + $Comp_AF = int($Comp_AF/3); + } + else + { + print "YOU WIPED OUT ONE OF MY ARMY PATROLS, BUT I DESTROYED\n"; + print "TWO NAVY BASES AND BOMBED THREE ARMY BASES.\n"; + $User_army = int($User_army/4); + $User_navy = int($User_navy/3); + $User_AF = int(2*$User_AF/3); + } +} + +print "\n\tYOU\tME\n"; +print "ARMY\t$User_army\t$Comp_army\n"; +print "NAVY\t$User_navy\t$Comp_navy\n"; +print "A. F.\t$User_AF\t$Comp_AF\n"; +do { + print "WHAT IS YOUR NEXT MOVE?\n"; + print "ARMY=1 NAVY=2 AIR FORCE=3\n"; + chomp($Attack_type = <>); +} while ($Attack_type < 1 && $Attack_type > 3); +do { + print "HOW MANY MEN\n"; + chomp($Attack_num = <>); +} while ($Attack_num < 0 + || ($Attack_type == 1 && $Attack_num > $User_army) + || ($Attack_type == 2 && $Attack_num > $User_navy) + || ($Attack_type == 3 && $Attack_num > $User_AF)); + +if ($Attack_num == 1) +{ + if ($Attack_num < $Comp_army/2) + { + print "I WIPED OUT YOUR ATTACK!\n"; + $User_army -= $Attack_num; + } + else + { + print "YOU DESTROYED MY ARMY!\n"; + $Comp_army = 0; + } +} +elsif ($Attack_num == 2) +{ + if ($Attack_num < $Comp_navy/2) + { + print "I SUNK TWO OF YOUR BATTLESHIPS, AND MY AIR FORCE\n"; + print "WIPED OUT YOUR UNGAURDED CAPITOL.\n"; + $User_army /= 4; + $User_navy /= 2; + } + else + { + print "YOUR NAVY SHOT DOWN THREE OF MY XIII PLANES,\n"; + print "AND SUNK THREE BATTLESHIPS.\n"; + $Comp_AF = 2*$Comp_AF/3; + $Comp_navy /= 2; + } +} +else # $Attack_num == 3 +{ + if ($Attack_num > $Comp_AF/2) + { + print "MY NAVY AND AIR FORCE IN A COMBINED ATTACK LEFT\n"; + print "YOUR COUNTRY IN SHAMBLES.\n"; + $User_army /= 3; + $User_navy /= 3; + $User_AF /= 3; + } + else + { + print "ONE OF YOUR PLANES CRASHED INTO MY HOUSE. I AM DEAD.\n"; + print "MY COUNTRY FELL APART.\n"; + $Comp_army = $Comp_navy = $Comp_AF = 0; + } +} + +print "\nFROM THE RESULTS OF BOTH OF YOUR ATTACKS,\n"; +my $total_user = $User_army+$User_navy+$User_AF; +my $total_comp = $Comp_army+$Comp_navy+$Comp_AF; +if ($total_user > 3/2*($total_comp)) +{ + print "YOU WON, OH! SHUCKS!!!!\n"; +} +elsif ($total_user < 2/3*($total_comp)) +{ + print "YOU LOST-I CONQUERED YOUR COUNTRY. IT SERVES YOU\n"; + print "RIGHT FOR PLAYING THIS STUPID GAME!!!\n"; +} +else +{ + print "THE TREATY OF PARIS CONCLUDED THAT WE TAKE OUR\n"; + print "RESPECTIVE COUNTRIES AND LIVE IN PEACE.\n"; +} +print "\n"; +exit(0); + +####################################################### + +sub s270 +{ + print "YOU SUNK ONE OF MY PATROL BOATS, BUT I WIPED OUT TWO\n"; + print "OF YOUR AIR FORCE BASES AND 3 ARMY BASES.\n"; + $User_army = int($User_army/3); + $User_AF = int($User_AF/3); + $Comp_navy = int(2*$Comp_navy/3); +} From e02a4ca5da107752ccbe61d0ff4e58197a303868 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Sat, 20 May 2023 17:12:48 +1000 Subject: [PATCH 133/141] Tidy up shot selection --- 77_Salvo/csharp/Game.cs | 18 ++++++------ 77_Salvo/csharp/Grid.cs | 14 +--------- .../csharp/Targetting/ComputerShotSelector.cs | 18 ++++++------ .../csharp/Targetting/HumanShotSelector.cs | 8 +++--- .../KnownHitsShotSelectionStrategy.cs | 10 +++---- .../Targetting/SearchPatternShotSelector.cs | 22 +++++++-------- .../Targetting/ShotSelectionStrategy.cs | 13 +++++---- 77_Salvo/csharp/Targetting/ShotSelector.cs | 28 ++++++++++++++----- 8 files changed, 67 insertions(+), 64 deletions(-) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index 95ccef23..6c0add12 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -22,8 +22,8 @@ internal class Game var computerGrid = new Grid(_random); var humanGrid = new Grid(_io); - var humanShotSelector = new HumanShotSelector(humanGrid, computerGrid, _io); - var computerShotSelector = new ComputerShotSelector(computerGrid, humanGrid, _random); + var humanShotSelector = new HumanShotSelector(humanGrid, _io); + var computerShotSelector = new ComputerShotSelector(computerGrid, _random); var startResponse = _io.ReadString(Prompts.Start); while (startResponse == Strings.WhereAreYourShips) { @@ -43,14 +43,14 @@ L1980: _io.Write(Strings.Turn(turnNumber)); L1990: var numberOfShots = humanShotSelector.NumberOfShots; L2220: _io.Write(Strings.YouHaveShots(numberOfShots)); if (numberOfShots == 0) { goto L2270; } -L2230: if (numberOfShots > computerGrid.UntriedSquareCount) +L2230: if (humanShotSelector.CanTargetAllRemainingSquares) { _io.WriteLine(Streams.YouHaveMoreShotsThanSquares); L2250: goto L2890; } - foreach (var shot1 in humanShotSelector.GetShots()) + foreach (var shot1 in humanShotSelector.GetShots(turnNumber)) { - if (computerGrid.IsHit(shot1, turnNumber, out var ship)) + if (computerGrid.IsHit(shot1, out var ship)) { _io.Write(Strings.YouHit(ship.Name)); } @@ -60,15 +60,15 @@ L2640: turnNumber++; L2660: _io.Write(Strings.Turn(turnNumber)); L2670: numberOfShots = computerShotSelector.NumberOfShots; L2840: _io.Write(Strings.IHaveShots(numberOfShots)); -L2850: if (humanGrid.UntriedSquareCount > numberOfShots) { goto L2880; } +L2850: if (!computerShotSelector.CanTargetAllRemainingSquares) { goto L2880; } L2860: _io.Write(Streams.IHaveMoreShotsThanSquares); L2270: _io.Write(Streams.IWon); return; -L2880: if (numberOfShots == 0) { goto L2960; } +L2880: if (numberOfShots > 0) { goto L2960; } L2890: _io.Write(Streams.YouWon); L2900: return; -L2960: temp = computerShotSelector.GetShots().ToArray(); +L2960: temp = computerShotSelector.GetShots(turnNumber).ToArray(); // display shots L3380: if (seeShotsResponse == "YES") { @@ -79,7 +79,7 @@ L3380: if (seeShotsResponse == "YES") } foreach (var shot in temp) { - if (humanGrid.IsHit(shot, turnNumber, out var ship)) + if (humanGrid.IsHit(shot, out var ship)) { _io.Write(Strings.IHit(ship.Name)); computerShotSelector.RecordHit(ship, turnNumber); diff --git a/77_Salvo/csharp/Grid.cs b/77_Salvo/csharp/Grid.cs index 0c84d688..f91ae43a 100644 --- a/77_Salvo/csharp/Grid.cs +++ b/77_Salvo/csharp/Grid.cs @@ -6,12 +6,6 @@ namespace Salvo; internal class Grid { private readonly List _ships; - private readonly Dictionary _shots = new(); - - internal Grid() - { - _ships = new(); - } internal Grid(IReadWrite io) { @@ -57,16 +51,10 @@ internal class Grid } } - internal int UntriedSquareCount => 100 - _shots.Count; internal IEnumerable Ships => _ships.AsEnumerable(); - internal bool WasTargetedAt(Position position, out int turnTargeted) - => _shots.TryGetValue(position, out turnTargeted); - - internal bool IsHit(Position position, int turnNumber, [NotNullWhen(true)] out Ship? ship) + internal bool IsHit(Position position, [NotNullWhen(true)] out Ship? ship) { - _shots[position] = turnNumber; - ship = _ships.FirstOrDefault(s => s.IsHit(position)); if (ship == null) { return false; } diff --git a/77_Salvo/csharp/Targetting/ComputerShotSelector.cs b/77_Salvo/csharp/Targetting/ComputerShotSelector.cs index 41b27a6d..5a5214dd 100644 --- a/77_Salvo/csharp/Targetting/ComputerShotSelector.cs +++ b/77_Salvo/csharp/Targetting/ComputerShotSelector.cs @@ -3,19 +3,19 @@ namespace Salvo.Targetting; internal class ComputerShotSelector : ShotSelector { private readonly KnownHitsShotSelectionStrategy _knownHitsStrategy; - private readonly SearchPatternShotSelector _searchPatternShotSelector; + private readonly SearchPatternShotSelectionStrategy _searchPatternStrategy; - internal ComputerShotSelector(Grid source, Grid target, IRandom random) - : base(source, target) + internal ComputerShotSelector(Grid source, IRandom random) + : base(source) { - _knownHitsStrategy = new KnownHitsShotSelectionStrategy(target); - _searchPatternShotSelector = new SearchPatternShotSelector(source, target, random); + _knownHitsStrategy = new KnownHitsShotSelectionStrategy(this); + _searchPatternStrategy = new SearchPatternShotSelectionStrategy(this, random); } - internal override IEnumerable GetShots() - { - return _knownHitsStrategy.GetShots(NumberOfShots) ?? _searchPatternShotSelector.GetShots(); - } + protected override IEnumerable GetShots() => GetSelectionStrategy().GetShots(NumberOfShots); internal void RecordHit(Ship ship, int turn) => _knownHitsStrategy.RecordHit(ship, turn); + + private ShotSelectionStrategy GetSelectionStrategy() + => _knownHitsStrategy.KnowsOfDamagedShips ? _knownHitsStrategy : _searchPatternStrategy; } diff --git a/77_Salvo/csharp/Targetting/HumanShotSelector.cs b/77_Salvo/csharp/Targetting/HumanShotSelector.cs index b4a0db13..f3e5fba2 100644 --- a/77_Salvo/csharp/Targetting/HumanShotSelector.cs +++ b/77_Salvo/csharp/Targetting/HumanShotSelector.cs @@ -4,13 +4,13 @@ internal class HumanShotSelector : ShotSelector { private readonly IReadWrite _io; - internal HumanShotSelector(Grid source, Grid target, IReadWrite io) - : base(source, target) + internal HumanShotSelector(Grid source, IReadWrite io) + : base(source) { _io = io; } - internal override IEnumerable GetShots() + protected override IEnumerable GetShots() { var shots = new Position[NumberOfShots]; @@ -19,7 +19,7 @@ internal class HumanShotSelector : ShotSelector while (true) { var position = _io.ReadValidPosition(); - if (Target.WasTargetedAt(position, out var turnTargeted)) + if (WasSelectedPreviously(position, out var turnTargeted)) { _io.WriteLine($"YOU SHOT THERE BEFORE ON TURN {turnTargeted}"); continue; diff --git a/77_Salvo/csharp/Targetting/KnownHitsShotSelectionStrategy.cs b/77_Salvo/csharp/Targetting/KnownHitsShotSelectionStrategy.cs index 917b232e..5e0b9d2c 100644 --- a/77_Salvo/csharp/Targetting/KnownHitsShotSelectionStrategy.cs +++ b/77_Salvo/csharp/Targetting/KnownHitsShotSelectionStrategy.cs @@ -4,15 +4,15 @@ internal class KnownHitsShotSelectionStrategy : ShotSelectionStrategy { private readonly List<(int Turn, Ship Ship)> _damagedShips = new(); - internal KnownHitsShotSelectionStrategy(Grid target) - : base(target) + internal KnownHitsShotSelectionStrategy(ShotSelector shotSelector) + : base(shotSelector) { } - internal IEnumerable? GetShots(int numberOfShots) - { - if (!_damagedShips.Any()) { return null; } + internal bool KnowsOfDamagedShips => _damagedShips.Any(); + internal override IEnumerable GetShots(int numberOfShots) + { var tempGrid = Position.All.ToDictionary(x => x, _ => 0); var shots = Enumerable.Range(1, numberOfShots).Select(x => new Position(x, x)).ToArray(); diff --git a/77_Salvo/csharp/Targetting/SearchPatternShotSelector.cs b/77_Salvo/csharp/Targetting/SearchPatternShotSelector.cs index 329a0a98..1eae88f8 100644 --- a/77_Salvo/csharp/Targetting/SearchPatternShotSelector.cs +++ b/77_Salvo/csharp/Targetting/SearchPatternShotSelector.cs @@ -1,30 +1,30 @@ namespace Salvo.Targetting; -internal class SearchPatternShotSelector : ShotSelector +internal class SearchPatternShotSelectionStrategy : ShotSelectionStrategy { private const int MaxSearchPatternAttempts = 100; private readonly IRandom _random; private readonly SearchPattern _searchPattern = new(); private readonly List _shots = new(); - internal SearchPatternShotSelector(Grid source, Grid target, IRandom random) - : base(source, target) + internal SearchPatternShotSelectionStrategy(ShotSelector shotSelector, IRandom random) + : base(shotSelector) { _random = random; } - internal override IEnumerable GetShots() + internal override IEnumerable GetShots(int numberOfShots) { _shots.Clear(); - while(_shots.Count < NumberOfShots) + while(_shots.Count < numberOfShots) { var (seed, _) = _random.NextShipPosition(); - SearchFrom(seed); + SearchFrom(numberOfShots, seed); } return _shots; } - private void SearchFrom(Position candidateShot) + private void SearchFrom(int numberOfShots, Position candidateShot) { var attemptsLeft = MaxSearchPatternAttempts; while (true) @@ -32,18 +32,18 @@ internal class SearchPatternShotSelector : ShotSelector _searchPattern.Reset(); if (attemptsLeft-- == 0) { return; } candidateShot = candidateShot.BringIntoRange(_random); - if (FindValidShots(ref candidateShot)) { return; } + if (FindValidShots(numberOfShots, ref candidateShot)) { return; } } } - private bool FindValidShots(ref Position candidateShot) + private bool FindValidShots(int numberOfShots, ref Position candidateShot) { while (true) { if (IsValidShot(candidateShot)) { _shots.Add(candidateShot); - if (_shots.Count == NumberOfShots) { return true; } + if (_shots.Count == numberOfShots) { return true; } } if (!_searchPattern.TryGetOffset(out var offset)) { return false; } candidateShot += offset; @@ -51,5 +51,5 @@ internal class SearchPatternShotSelector : ShotSelector } private bool IsValidShot(Position candidate) - => candidate.IsInRange && !Target.WasTargetedAt(candidate, out _) && !_shots.Contains(candidate); + => candidate.IsInRange && !WasSelectedPreviously(candidate) && !_shots.Contains(candidate); } \ No newline at end of file diff --git a/77_Salvo/csharp/Targetting/ShotSelectionStrategy.cs b/77_Salvo/csharp/Targetting/ShotSelectionStrategy.cs index f98c7b4f..b4cf14b1 100644 --- a/77_Salvo/csharp/Targetting/ShotSelectionStrategy.cs +++ b/77_Salvo/csharp/Targetting/ShotSelectionStrategy.cs @@ -2,15 +2,16 @@ namespace Salvo.Targetting; internal abstract class ShotSelectionStrategy { - private readonly Grid _target; - protected ShotSelectionStrategy(Grid target) + private readonly ShotSelector _shotSelector; + protected ShotSelectionStrategy(ShotSelector shotSelector) { - _target = target; + _shotSelector = shotSelector; } - protected bool WasSelectedPreviously(Position position) - => _target.WasTargetedAt(position, out _); + internal abstract IEnumerable GetShots(int numberOfShots); + + protected bool WasSelectedPreviously(Position position) => _shotSelector.WasSelectedPreviously(position); protected bool WasSelectedPreviously(Position position, out int turn) - => _target.WasTargetedAt(position, out turn); + => _shotSelector.WasSelectedPreviously(position, out turn); } diff --git a/77_Salvo/csharp/Targetting/ShotSelector.cs b/77_Salvo/csharp/Targetting/ShotSelector.cs index 5aaa4753..95a5c4bd 100644 --- a/77_Salvo/csharp/Targetting/ShotSelector.cs +++ b/77_Salvo/csharp/Targetting/ShotSelector.cs @@ -2,16 +2,30 @@ namespace Salvo.Targetting; internal abstract class ShotSelector { - internal ShotSelector(Grid source, Grid target) + private readonly Grid _source; + private readonly Dictionary _previousShots = new(); + + internal ShotSelector(Grid source) { - Source = source; - Target = target; + _source = source; } - protected Grid Source { get; } - protected Grid Target { get; } + internal int NumberOfShots => _source.Ships.Sum(s => s.Shots); + internal bool CanTargetAllRemainingSquares => NumberOfShots >= 100 - _previousShots.Count; - internal int NumberOfShots => Source.Ships.Sum(s => s.Shots); + internal bool WasSelectedPreviously(Position position) => _previousShots.ContainsKey(position); - internal abstract IEnumerable GetShots(); + internal bool WasSelectedPreviously(Position position, out int turn) + => _previousShots.TryGetValue(position, out turn); + + internal IEnumerable GetShots(int turnNumber) + { + foreach (var shot in GetShots()) + { + _previousShots.Add(shot, turnNumber); + yield return shot; + } + } + + protected abstract IEnumerable GetShots(); } From 345584819531546f9beccc110b0fe02eb44011f5 Mon Sep 17 00:00:00 2001 From: kbrannen Date: Sat, 20 May 2023 02:34:58 -0500 Subject: [PATCH 134/141] added 15-boxing for perl --- 15_Boxing/perl/boxing.pl | 252 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 15_Boxing/perl/boxing.pl diff --git a/15_Boxing/perl/boxing.pl b/15_Boxing/perl/boxing.pl new file mode 100644 index 00000000..5068e58a --- /dev/null +++ b/15_Boxing/perl/boxing.pl @@ -0,0 +1,252 @@ +#!/usr/bin/perl + +# Boxing program in Perl +# Required extensive restructuring to remove all of the GOTO's. +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# globals +my $Opp_won = 0; # num rounds opponent has won +my $You_won = 0; # num rounds you have won +my $Opp_name = ""; # opponent name +my $Your_name = ""; # your name +my $Your_best = 0; # your best punch +my $Your_worst = 0; # your worst punch +my $Opp_best; # opponent best punch +my $Opp_worst; # opponent worst punch +my $Opp_damage; # opponent damage ? +my $Your_damage; # your damage ? + +sub get_punch +{ + my $prompt = shift; + my $p; + while (1) + { + print "$prompt: "; + chomp($p = int(<>)); + last if ($p >= 1 && $p <= 4); + print "DIFFERENT PUNCHES ARE: (1) FULL SWING; (2) HOOK; (3) UPPERCUT; (4) JAB.\n"; + } + return $p; +} + +print "\n"; +print " " x 33, "BOXING\n"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; + +print "BOXING OLYMPIC STYLE (3 ROUNDS -- 2 OUT OF 3 WINS)\n\n"; +print "WHAT IS YOUR OPPONENT'S NAME: "; +chomp($Opp_name = <>); +print "INPUT YOUR MAN'S NAME: "; +chomp($Your_name = <>); +print "DIFFERENT PUNCHES ARE: (1) FULL SWING; (2) HOOK; (3) UPPERCUT; (4) JAB.\n"; +$Your_best = get_punch("WHAT IS YOUR MANS BEST"); +$Your_worst = get_punch("WHAT IS HIS VULNERABILITY"); + +do { + $Opp_best = int(4*rand(1)+1); + $Opp_worst = int(4*rand(1)+1); +} while ($Opp_best == $Opp_worst); +print "$Opp_name\'S ADVANTAGE IS $Opp_best AND VULNERABILITY IS SECRET.\n\n"; + +for my $R (1 .. 3) # rounds +{ + last if ($Opp_won >= 2 || $You_won >= 2); + $Opp_damage = 0; + $Your_damage = 0; + print "ROUND $R BEGINS...\n"; + for my $R1 (1 .. 7) # 7 events per round? + { + if (int(10*rand(1)+1) <= 5) + { + my $your_punch = get_punch("$Your_name\'S PUNCH"); + $Opp_damage += 2 if ($your_punch == $Your_best); + + if ($your_punch == 1) { punch1(); } + elsif ($your_punch == 2) { punch2(); } + elsif ($your_punch == 3) { punch3(); } + else { punch4(); } + next; + } + + my $Opp_punch = int(4*rand(1)+1); + $Your_damage += 2 if ($Opp_punch == $Opp_best); + + if ($Opp_punch == 1) { opp1(); } + elsif ($Opp_punch == 2) { opp2(); } + elsif ($Opp_punch == 3) { opp3(); } + else { opp4(); } + } + + if ($Opp_damage > $Your_damage) + { + print "\n$Your_name WINS ROUND $R\n\n"; + $You_won++; + } + else + { + print "\n$Opp_name WINS ROUND $R\n\n"; + $Opp_won++; + } +} + +if ($Opp_won >= 2) +{ + done("$Opp_name WINS (NICE GOING, $Opp_name)."); +} + +#else # if ($You_won >= 2) +done("$Your_name AMAZINGLY WINS!!"); + +################################################### + +sub done +{ + my $msg = shift; + print $msg; + print "\n\nAND NOW GOODBYE FROM THE OLYMPIC ARENA.\n\n"; + exit(0); +} + +sub punch1 +{ + # $your_punch == 1, full swing + print "$Your_name SWINGS AND "; + if ($Opp_worst == 4 || int(30*rand(1)+1) < 10) + { + print "HE CONNECTS!\n"; + if ($Opp_damage > 35) + { + done("$Opp_name IS KNOCKED COLD AND $Your_name IS THE WINNER AND CHAMP! "); + } + $Opp_damage += 15; + } + else + { + print "HE MISSES\n"; + print "\n\n" if ($Opp_damage != 1); + } +} + +sub punch2 +{ + # $your_punch == 2, hook + print "$Your_name GIVES THE HOOK... "; + if ($Opp_worst == 2) + { + $Opp_damage += 7; + return; + } + if (int(2*rand(1)+1) == 1) + { + print "BUT IT'S BLOCKED!!!!!!!!!!!!!\n"; + } + else + { + print "CONNECTS...\n"; + $Opp_damage += 7; + } +} + +sub punch3 +{ + # $your_punch == 3, uppercut + print "$Your_name TRIES AN UPPERCUT "; + if ($Opp_worst == 3 || int(100*rand(1)+1) < 51) + { + print "AND HE CONNECTS!\n"; + $Opp_damage += 4; + } + else + { + print "AND IT'S BLOCKED (LUCKY BLOCK!)\n"; + } +} + +sub punch4 +{ + # $your_punch == 4, jab + print "$Your_name JABS AT $Opp_name\'S HEAD "; + if ($Opp_worst == 4 || (int(8*rand(1)+1)) >= 4) + { + $Opp_damage += 3; + print "\n"; + } + else + { + print "IT'S BLOCKED.\n"; + } +} + +sub opp1 +{ + # opp_punch == 1 + print "$Opp_name TAKES A FULL SWING AND "; + if ($Your_worst == 1 || int(60*rand(1)+1) < 30) + { + print " POW!!!!! HE HITS HIM RIGHT IN THE FACE!\n"; + if ($Your_damage > 35) + { + done("$Your_name IS KNOCKED COLD AND $Opp_name IS THE WINNER AND CHAMP!"); + } + $Your_damage += 15; + } + else + { + print " IT'S BLOCKED!\n"; + } +} + +sub opp2 +{ + # opp_punch == 2 + print "$Opp_name GETS $Your_name IN THE JAW (OUCH!)\n"; + $Your_damage += 7; + print "....AND AGAIN!\n"; + $Your_damage += 5; + if ($Your_damage > 35) + { + done("$Your_name IS KNOCKED COLD AND $Opp_name IS THE WINNER AND CHAMP!"); + } + print "\n"; + # 2 continues into opp_punch == 3 + opp3(); +} + +sub opp3() +{ + # opp_punch == 3 + print "$Your_name IS ATTACKED BY AN UPPERCUT (OH,OH)...\n"; + if ($Your_worst != 3 && int(200*rand(1)+1) > 75) + { + print " BLOCKS AND HITS $Opp_name WITH A HOOK.\n"; + $Opp_damage += 5; + } + else + { + print "AND $Opp_name CONNECTS...\n"; + $Your_damage += 8; + } +} + +sub opp4 +{ + # opp_punch == 4 + print "$Opp_name JABS AND "; + if ($Your_worst == 4) + { + $Your_damage += 5; + } + elsif (int(7*rand(1)+1) > 4) + { + print " BLOOD SPILLS !!!\n"; + $Your_damage += 5; + } + else + { + print "IT'S BLOCKED!\n"; + } +} From ce013cfad91705e8a26010a3a8e34a3de0745798 Mon Sep 17 00:00:00 2001 From: kbrannen Date: Sun, 21 May 2023 15:12:41 -0500 Subject: [PATCH 135/141] added checkers for perl --- 23_Checkers/perl/README.md | 6 + 23_Checkers/perl/checkers.pl | 343 +++++++++++++++++++++++++++++++++++ 2 files changed, 349 insertions(+) create mode 100755 23_Checkers/perl/checkers.pl diff --git a/23_Checkers/perl/README.md b/23_Checkers/perl/README.md index e69c8b81..4323cf4a 100644 --- a/23_Checkers/perl/README.md +++ b/23_Checkers/perl/README.md @@ -1,3 +1,9 @@ Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) Conversion to [Perl](https://www.perl.org/) + +Note: This version has lines and columns numbers to help you with choosing the cell +to move from and to, so you don't have to continually count. It also puts a "." only for +blank cells you can move to, which I think makes for a more pleasing look and makes +it easier to play. If you want the original behavior, start the program with an arg +of "-o" for the original behavior. diff --git a/23_Checkers/perl/checkers.pl b/23_Checkers/perl/checkers.pl new file mode 100755 index 00000000..9e87f1fe --- /dev/null +++ b/23_Checkers/perl/checkers.pl @@ -0,0 +1,343 @@ +#!/usr/bin/perl + +# Boxing program in Perl +# Started with checkers.annotated.bas +# Translated by Kevin Brannen (kbrannen) + +use strict; +use warnings; + +# globals +# +# The current move: (rating, current x, current y, new x, new y) +# 'rating' represents how good the move is; higher is better. +my @r = (-99); # (4); # Start with minimum score +# The board. Pieces are represented by numeric values: +# +# - 0 = empty square +# - -1,-2 = X (-1 for regular piece, -2 for king) +# - 1,2 = O (1 for regular piece, 2 for king) +# +# This program's player ("me") plays X. +my @s; # (7,7) +# chars to print for the board, add 2 to the board value as an index to the char +my @chars = ("X*", "X", ".", "O", "O*"); +my $g = -1; # constant holding -1 +my $winner = ""; +my $upgrade = shift(@ARGV) // ""; +$upgrade = $upgrade eq "-o" ? 0 : 1; + +##### + +print "\n"; +print " " x 32, "CHECKERS\n"; +print " " x 15, "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n\n\n"; + + +print "THIS IS THE GAME OF CHECKERS. THE COMPUTER IS X,\n"; +print "AND YOU ARE O. THE COMPUTER WILL MOVE FIRST.\n"; +print "SQUARES ARE REFERRED TO BY A COORDINATE SYSTEM.\n"; +print "(0,0) IS THE LOWER LEFT CORNER\n"; +print "(0,7) IS THE UPPER LEFT CORNER\n"; +print "(7,0) IS THE LOWER RIGHT CORNER\n"; +print "(7,7) IS THE UPPER RIGHT CORNER\n"; +print "THE COMPUTER WILL TYPE '+TO' WHEN YOU HAVE ANOTHER\n"; +print "JUMP. TYPE TWO NEGATIVE NUMBERS IF YOU CANNOT JUMP.\n\n\n"; + +# Initialize the board. Data is 2 length-wise strips repeated. +my @data = (); +for (1 .. 32) { push(@data, (1,0,1,0,0,0,-1,0, 0,1,0,0,0,-1,0,-1)); } +for my $x (0 .. 7) +{ + for my $y (0 .. 7) + { + $s[$x][$y] = shift(@data); + } +} + +# Start of game loop. First, my turn. +while (1) +{ + + # For each square on the board, search for one of my pieces + # and if it can make the best move so far, store that move in 'r' + for my $x (0 .. 7) + { + for my $y (0 .. 7) + { + # Skip if this is empty or an opponent's piece + next if ($s[$x][$y] > -1); + + # If this is one of my ordinary pieces, analyze possible + # forward moves. + if ($s[$x][$y] == -1) + { + for (my $a = -1 ; $a <= 1 ; $a +=2) + { + $b = $g; + find_move($x, $y, $a, $b); + } + } + + # If this is one of my kings, analyze possible forward + # and backward moves. + if ($s[$x][$y] == -2) + { + for (my $a = -1 ; $a <= 1 ; $a += 2) + { + for (my $b = -1 ; $a <= 1 ; $b += 2) { find_move($x, $y, $a, $b); } + } + } + } + } + + + if ($r[0] == -99) # Game is lost if no move could be found. + { + $winner = "you"; + last; + } + + # Print the computer's move. (Note: chr$(30) is an ASCII RS + # (record separator) code; probably no longer relevant.) + print "FROM $r[1],$r[2] TO $r[3],$r[4] "; + $r[0] = -99; + + # Make the computer's move. If the piece finds its way to the + # end of the board, crown it. + LOOP1240: { + if ($r[4] == 0) + { + $s[$r[3]][$r[4]] = -2; + last LOOP1240; + } + $s[$r[3]][$r[4]] = $s[$r[1]][$r[2]]; + $s[$r[1]][$r[2]] = 0; + + # If the piece has jumped 2 squares, it means the computer has + # taken an opponents' piece. + if (abs($r[1] - $r[3]) == 2) + { + $s [($r[1]+$r[3])/2] [($r[2]+$r[4])/2] = 0; # Delete the opponent's piece + + # See if we can jump again. Evaluate all possible moves. + my $x = $r[3]; + my $y = $r[4]; + for (my $a = -2 ; $a <= 2 ; $a += 4) + { + if ($s[$x][$y] == -1) + { + $b = -2; + eval_move($x, $y, $a, $b); + } + if ($s[$x][$y] == -2) + { + for (my $b = -2 ; $b <= 2 ; $b += 4) { eval_move($x, $y, $a, $b); } + } + } + + # If we've found a move, go back and make that one as well + if ($r[0] != -99) + { + print "TO $r[3], $r[4] "; + $r[0] = -99; + next LOOP1240; + } + } + } # LOOP1240 + + # Now, print the board + print "\n\n\n"; + for (my $y = 7 ; $y >= 0 ; $y--) + { + my $line = ""; + $line = "$y|" if ($upgrade); + for my $x (0 .. 7) + { + my $c = $chars[$s[$x][$y] + 2]; + $c = ' ' if ($upgrade && (($y % 2 == 0 && $x % 2 == 1) || ($y % 2 == 1 && $x % 2 == 0))); + $line = tab($line, 5*$x+7, $c); + } + print $line; + print " \n\n"; + } + print " _ _ _ _ _ _ _ _\n" if ($upgrade); + print " 0 1 2 3 4 5 6 7\n" if ($upgrade); + print "\n"; + + # Check if either player is out of pieces. If so, announce the + # winner. + my ($z, $t) = (0, 0); + for my $x (0 .. 7) + { + for my $y (0 .. 7) + { + if ($s[$x][$y] == 1 || $s[$x][$y] == 2) { $z = 1; } + if ($s[$x][$y] == -1 || $s[$x][$y] == -2) { $t = 1; } + } + } + if ($z != 1) { $winner = "comp"; last; } + if ($t != 1) { $winner = "you"; last; } + + # Prompt the player for their move. + ($z, $t) = (0, 0); + my ($x, $y, $e, $h, $a, $b); + do { + print "FROM: "; + chomp(my $ans = <>); + ($e,$h) = split(/[, ]/, $ans); + $x = $e; + $y = $h; + } while ($s[$x][$y] <= 0); + do { + print "TO: "; + chomp(my $ans = <>); + ($a,$b) = split(/[, ]/, $ans); + $x = $a; + $y = $b; + } while (!($s[$x][$y] == 0 && abs($a-$e) <= 2 && abs($a-$e) == abs($b-$h))); + + LOOP1750: { + # Make the move and stop unless it might be a jump. + $s[$a][$b] = $s[$e][$h]; + $s[$e][$h] = 0; + if (abs($e-$a) != 2) { last LOOP1750; } + + # Remove the piece jumped over + $s[($e+$a)/2][($h+$b)/2] = 0; + + # Prompt for another move; -1 means player can't, so I've won. + # Keep prompting until there's a valid move or the player gives + # up. + my ($a1, $b1); + do { + print "+TO "; + chomp(my $ans = <>); + ($a1,$b1) = split(/[, ]/, $ans); + if ($a1 < 0) { last LOOP1750; } + } while ($s[$a1][$b1] != 0 || abs($a1-$a) != 2 || abs($b1-$b) != 2); + + # Update the move variables to correspond to the next jump + $e = $a; + $h = $b; + $a = $a1; + $b = $b1; + } + + # If the player has reached the end of the board, crown this piece + if ($b == 7) { $s[$a][$b] = 2; } + + # And play the next turn. +} + +# Endgame: +print "\n", ($winner eq "you" ? "YOU" : "I"), " WIN\n"; +exit(0); + +########################################### + +# deal with basic's tab() for line positioning +# line = line string we're starting with +# pos = position to start writing +# s = string to write +# returns the resultant string, which might not have been changed +sub tab +{ + my ($line, $pos, $s) = @_; + my $len = length($line); + # if curser is past position, do nothing + if ($len <= $pos) { $line .= " " x ($pos - $len) . $s; } + return $line; +} + +# Analyze a move from (x,y) to (x+a, y+b) and schedule it if it's +# the best candidate so far. +sub find_move +{ + my ($x, $y, $a, $b) = @_; + my $u = $x+$a; + my $v = $y+$b; + + # Done if it's off the board + return if ($u < 0 || $u > 7 || $v < 0 || $ v> 7); + + # Consider the destination if it's empty + eval_jump($x, $y, $u, $v) if ($s[$u][$v] == 0); + + # If it's got an opponent's piece, jump it instead + if ($s[$u][$v] > 0) + { + + # Restore u and v, then return if it's off the board + $u += $a; + $v += $b; + return if ($u < 0 || $v < 0 || $u > 7 || $v > 7); + + # Otherwise, consider u,v + eval_jump($x, $y, $u, $v) if ($s[$u][$v] == 0); + } +} + +# Evaluate jumping (x,y) to (u,v). +# +# Computes a score for the proposed move and if it's higher +# than the best-so-far move, uses that instead by storing it +# and its score in @r. +sub eval_jump +{ + my ($x, $y, $u, $v) = @_; + + # q is the score; it starts at 0 + my $q = 0; + + # +2 if it promotes this piece + $q += 2 if ($v == 0 && $s[$x][$y] == -1); + + # +5 if it takes an opponent's piece + $q += 5 if (abs($y-$v) == 2); + + # -2 if the piece is moving away from the top boundary + $q -= 2 if ($y == 7); + + # +1 for putting the piece against a vertical boundary + $q++ if ($u == 0 || $u == 7); + + for (my $c = -1 ; $c <= 1 ; $c += 2) + { + next if ($u+$c < 0 || $u+$c > 7 || $v+$g < 0); + + # +1 for each adjacent friendly piece + if ($s[$u+$c][$v+$g] < 0) + { + $q++; + next; + } + + # Prevent out-of-bounds testing + next if ($u-$c < 0 || $u-$c > 7 || $v-$g > 7); + + # -2 for each opponent piece that can now take this piece here + $q -= 2 if ($s[$u+$c][$v+$g] > 0 && ($s[$u-$c][$v-$g] == 0 || ($u-$c == $x && $v-$g == $y))); + } + + # Use this move if it's better than the previous best + if ($q > $r[0]) + { + $r[0] = $q; + $r[1] = $x; + $r[2] = $y; + $r[3] = $u; + $r[4] = $v; + } +} + +# If (u,v) is in the bounds, evaluate it as a move using +# the sub at 910, so storing eval in @r. +sub eval_move +{ + my ($x, $y, $a, $b) = @_; + my $u = $x+$a; + my $v = $y+$b; + return if ($u < 0 || $u > 7 || $v < 0 || $v > 7); + eval_jump($x, $y, $u, $v) if ($s[$u][$v] == 0 && $s[$x+$a/2][$y+$b/2] > 0); +} From b45ad8544c3b67c0c5751f1a25436b68fcba41aa Mon Sep 17 00:00:00 2001 From: kbrannen Date: Sun, 21 May 2023 15:14:10 -0500 Subject: [PATCH 136/141] fixed typo --- 23_Checkers/perl/checkers.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/23_Checkers/perl/checkers.pl b/23_Checkers/perl/checkers.pl index 9e87f1fe..94643f76 100755 --- a/23_Checkers/perl/checkers.pl +++ b/23_Checkers/perl/checkers.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl -# Boxing program in Perl +# Checkers program in Perl # Started with checkers.annotated.bas # Translated by Kevin Brannen (kbrannen) From 1ae84a67df0bb5d4f92f218160ab43518168824c Mon Sep 17 00:00:00 2001 From: kbrannen Date: Sun, 21 May 2023 15:33:25 -0500 Subject: [PATCH 137/141] cleaned up --- 23_Checkers/perl/checkers.pl | 134 +++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 63 deletions(-) diff --git a/23_Checkers/perl/checkers.pl b/23_Checkers/perl/checkers.pl index 94643f76..46b8f2d4 100755 --- a/23_Checkers/perl/checkers.pl +++ b/23_Checkers/perl/checkers.pl @@ -11,7 +11,7 @@ use warnings; # # The current move: (rating, current x, current y, new x, new y) # 'rating' represents how good the move is; higher is better. -my @r = (-99); # (4); # Start with minimum score +my @ratings = (-99); # (4); # Start with minimum score # The board. Pieces are represented by numeric values: # # - 0 = empty square @@ -19,10 +19,10 @@ my @r = (-99); # (4); # Start with minimum score # - 1,2 = O (1 for regular piece, 2 for king) # # This program's player ("me") plays X. -my @s; # (7,7) +my @board; # (7,7) # chars to print for the board, add 2 to the board value as an index to the char my @chars = ("X*", "X", ".", "O", "O*"); -my $g = -1; # constant holding -1 +my $neg1 = -1; # constant holding -1 my $winner = ""; my $upgrade = shift(@ARGV) // ""; $upgrade = $upgrade eq "-o" ? 0 : 1; @@ -42,7 +42,8 @@ print "(0,7) IS THE UPPER LEFT CORNER\n"; print "(7,0) IS THE LOWER RIGHT CORNER\n"; print "(7,7) IS THE UPPER RIGHT CORNER\n"; print "THE COMPUTER WILL TYPE '+TO' WHEN YOU HAVE ANOTHER\n"; -print "JUMP. TYPE TWO NEGATIVE NUMBERS IF YOU CANNOT JUMP.\n\n\n"; +print "JUMP. TYPE TWO NEGATIVE NUMBERS IF YOU CANNOT JUMP.\n"; +print "ENTER YOUR MOVE POSITION LIKE '0 0' OR '0,0'.\n\n\n"; # Initialize the board. Data is 2 length-wise strips repeated. my @data = (); @@ -51,7 +52,7 @@ for my $x (0 .. 7) { for my $y (0 .. 7) { - $s[$x][$y] = shift(@data); + $board[$x][$y] = shift(@data); } } @@ -66,22 +67,22 @@ while (1) for my $y (0 .. 7) { # Skip if this is empty or an opponent's piece - next if ($s[$x][$y] > -1); + next if ($board[$x][$y] > -1); # If this is one of my ordinary pieces, analyze possible # forward moves. - if ($s[$x][$y] == -1) + if ($board[$x][$y] == -1) { for (my $a = -1 ; $a <= 1 ; $a +=2) { - $b = $g; + $b = $neg1; find_move($x, $y, $a, $b); } } # If this is one of my kings, analyze possible forward # and backward moves. - if ($s[$x][$y] == -2) + if ($board[$x][$y] == -2) { for (my $a = -1 ; $a <= 1 ; $a += 2) { @@ -92,7 +93,7 @@ while (1) } - if ($r[0] == -99) # Game is lost if no move could be found. + if ($ratings[0] == -99) # Game is lost if no move could be found. { $winner = "you"; last; @@ -100,47 +101,47 @@ while (1) # Print the computer's move. (Note: chr$(30) is an ASCII RS # (record separator) code; probably no longer relevant.) - print "FROM $r[1],$r[2] TO $r[3],$r[4] "; - $r[0] = -99; + print "FROM $ratings[1],$ratings[2] TO $ratings[3],$ratings[4] "; + $ratings[0] = -99; # Make the computer's move. If the piece finds its way to the # end of the board, crown it. LOOP1240: { - if ($r[4] == 0) + if ($ratings[4] == 0) { - $s[$r[3]][$r[4]] = -2; + $board[$ratings[3]][$ratings[4]] = -2; last LOOP1240; } - $s[$r[3]][$r[4]] = $s[$r[1]][$r[2]]; - $s[$r[1]][$r[2]] = 0; + $board[$ratings[3]][$ratings[4]] = $board[$ratings[1]][$ratings[2]]; + $board[$ratings[1]][$ratings[2]] = 0; # If the piece has jumped 2 squares, it means the computer has # taken an opponents' piece. - if (abs($r[1] - $r[3]) == 2) + if (abs($ratings[1] - $ratings[3]) == 2) { - $s [($r[1]+$r[3])/2] [($r[2]+$r[4])/2] = 0; # Delete the opponent's piece + $board [($ratings[1]+$ratings[3])/2] [($ratings[2]+$ratings[4])/2] = 0; # Delete the opponent's piece # See if we can jump again. Evaluate all possible moves. - my $x = $r[3]; - my $y = $r[4]; + my $x = $ratings[3]; + my $y = $ratings[4]; for (my $a = -2 ; $a <= 2 ; $a += 4) { - if ($s[$x][$y] == -1) + if ($board[$x][$y] == -1) { $b = -2; eval_move($x, $y, $a, $b); } - if ($s[$x][$y] == -2) + if ($board[$x][$y] == -2) { for (my $b = -2 ; $b <= 2 ; $b += 4) { eval_move($x, $y, $a, $b); } } } # If we've found a move, go back and make that one as well - if ($r[0] != -99) + if ($ratings[0] != -99) { - print "TO $r[3], $r[4] "; - $r[0] = -99; + print "TO $ratings[3], $ratings[4] "; + $ratings[0] = -99; next LOOP1240; } } @@ -154,7 +155,7 @@ while (1) $line = "$y|" if ($upgrade); for my $x (0 .. 7) { - my $c = $chars[$s[$x][$y] + 2]; + my $c = $chars[$board[$x][$y] + 2]; $c = ' ' if ($upgrade && (($y % 2 == 0 && $x % 2 == 1) || ($y % 2 == 1 && $x % 2 == 0))); $line = tab($line, 5*$x+7, $c); } @@ -172,8 +173,8 @@ while (1) { for my $y (0 .. 7) { - if ($s[$x][$y] == 1 || $s[$x][$y] == 2) { $z = 1; } - if ($s[$x][$y] == -1 || $s[$x][$y] == -2) { $t = 1; } + if ($board[$x][$y] == 1 || $board[$x][$y] == 2) { $z = 1; } + if ($board[$x][$y] == -1 || $board[$x][$y] == -2) { $t = 1; } } } if ($z != 1) { $winner = "comp"; last; } @@ -183,39 +184,33 @@ while (1) ($z, $t) = (0, 0); my ($x, $y, $e, $h, $a, $b); do { - print "FROM: "; - chomp(my $ans = <>); - ($e,$h) = split(/[, ]/, $ans); + ($e,$h) = get_pos("FROM:"); $x = $e; $y = $h; - } while ($s[$x][$y] <= 0); + } while ($board[$x][$y] <= 0); do { - print "TO: "; - chomp(my $ans = <>); - ($a,$b) = split(/[, ]/, $ans); + ($a,$b) = get_pos("TO:"); $x = $a; $y = $b; - } while (!($s[$x][$y] == 0 && abs($a-$e) <= 2 && abs($a-$e) == abs($b-$h))); + } while (!($board[$x][$y] == 0 && abs($a-$e) <= 2 && abs($a-$e) == abs($b-$h))); LOOP1750: { # Make the move and stop unless it might be a jump. - $s[$a][$b] = $s[$e][$h]; - $s[$e][$h] = 0; + $board[$a][$b] = $board[$e][$h]; + $board[$e][$h] = 0; if (abs($e-$a) != 2) { last LOOP1750; } # Remove the piece jumped over - $s[($e+$a)/2][($h+$b)/2] = 0; + $board[($e+$a)/2][($h+$b)/2] = 0; # Prompt for another move; -1 means player can't, so I've won. # Keep prompting until there's a valid move or the player gives # up. my ($a1, $b1); do { - print "+TO "; - chomp(my $ans = <>); - ($a1,$b1) = split(/[, ]/, $ans); + ($a1,$b1) = get_pos("+TO:"); if ($a1 < 0) { last LOOP1750; } - } while ($s[$a1][$b1] != 0 || abs($a1-$a) != 2 || abs($b1-$b) != 2); + } while ($board[$a1][$b1] != 0 || abs($a1-$a) != 2 || abs($b1-$b) != 2); # Update the move variables to correspond to the next jump $e = $a; @@ -225,7 +220,7 @@ while (1) } # If the player has reached the end of the board, crown this piece - if ($b == 7) { $s[$a][$b] = 2; } + if ($b == 7) { $board[$a][$b] = 2; } # And play the next turn. } @@ -236,6 +231,19 @@ exit(0); ########################################### +# make sure we get a 2 value position +sub get_pos +{ + my $prompt = shift; + my ($p1, $p2); + do { + print "$prompt "; + chomp(my $ans = <>); + ($p1,$p2) = split(/[, ]/, $ans); + } while (!defined($p1) || !defined($p2) || $p1 < -1 || $p2 < -1 || $p1 > 7 || $p2 > 7); + return ($p1,$p2); +} + # deal with basic's tab() for line positioning # line = line string we're starting with # pos = position to start writing @@ -243,10 +251,10 @@ exit(0); # returns the resultant string, which might not have been changed sub tab { - my ($line, $pos, $s) = @_; + my ($line, $pos, $str) = @_; my $len = length($line); # if curser is past position, do nothing - if ($len <= $pos) { $line .= " " x ($pos - $len) . $s; } + if ($len <= $pos) { $line .= " " x ($pos - $len) . $str; } return $line; } @@ -262,10 +270,10 @@ sub find_move return if ($u < 0 || $u > 7 || $v < 0 || $ v> 7); # Consider the destination if it's empty - eval_jump($x, $y, $u, $v) if ($s[$u][$v] == 0); + eval_jump($x, $y, $u, $v) if ($board[$u][$v] == 0); # If it's got an opponent's piece, jump it instead - if ($s[$u][$v] > 0) + if ($board[$u][$v] > 0) { # Restore u and v, then return if it's off the board @@ -274,7 +282,7 @@ sub find_move return if ($u < 0 || $v < 0 || $u > 7 || $v > 7); # Otherwise, consider u,v - eval_jump($x, $y, $u, $v) if ($s[$u][$v] == 0); + eval_jump($x, $y, $u, $v) if ($board[$u][$v] == 0); } } @@ -282,7 +290,7 @@ sub find_move # # Computes a score for the proposed move and if it's higher # than the best-so-far move, uses that instead by storing it -# and its score in @r. +# and its score in @ratings. sub eval_jump { my ($x, $y, $u, $v) = @_; @@ -291,7 +299,7 @@ sub eval_jump my $q = 0; # +2 if it promotes this piece - $q += 2 if ($v == 0 && $s[$x][$y] == -1); + $q += 2 if ($v == 0 && $board[$x][$y] == -1); # +5 if it takes an opponent's piece $q += 5 if (abs($y-$v) == 2); @@ -304,40 +312,40 @@ sub eval_jump for (my $c = -1 ; $c <= 1 ; $c += 2) { - next if ($u+$c < 0 || $u+$c > 7 || $v+$g < 0); + next if ($u+$c < 0 || $u+$c > 7 || $v+$neg1 < 0); # +1 for each adjacent friendly piece - if ($s[$u+$c][$v+$g] < 0) + if ($board[$u+$c][$v+$neg1] < 0) { $q++; next; } # Prevent out-of-bounds testing - next if ($u-$c < 0 || $u-$c > 7 || $v-$g > 7); + next if ($u-$c < 0 || $u-$c > 7 || $v-$neg1 > 7); # -2 for each opponent piece that can now take this piece here - $q -= 2 if ($s[$u+$c][$v+$g] > 0 && ($s[$u-$c][$v-$g] == 0 || ($u-$c == $x && $v-$g == $y))); + $q -= 2 if ($board[$u+$c][$v+$neg1] > 0 && ($board[$u-$c][$v-$neg1] == 0 || ($u-$c == $x && $v-$neg1 == $y))); } # Use this move if it's better than the previous best - if ($q > $r[0]) + if ($q > $ratings[0]) { - $r[0] = $q; - $r[1] = $x; - $r[2] = $y; - $r[3] = $u; - $r[4] = $v; + $ratings[0] = $q; + $ratings[1] = $x; + $ratings[2] = $y; + $ratings[3] = $u; + $ratings[4] = $v; } } # If (u,v) is in the bounds, evaluate it as a move using -# the sub at 910, so storing eval in @r. +# the sub at 910, so storing eval in @ratings. sub eval_move { my ($x, $y, $a, $b) = @_; my $u = $x+$a; my $v = $y+$b; return if ($u < 0 || $u > 7 || $v < 0 || $v > 7); - eval_jump($x, $y, $u, $v) if ($s[$u][$v] == 0 && $s[$x+$a/2][$y+$b/2] > 0); + eval_jump($x, $y, $u, $v) if ($board[$u][$v] == 0 && $board[$x+$a/2][$y+$b/2] > 0); } From 1d3fd94ae6a80ab75c5082d7974533660677ef77 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Mon, 22 May 2023 08:42:22 +1000 Subject: [PATCH 138/141] Simplify shot selection and evaluation --- 77_Salvo/csharp/{Grid.cs => Fleet.cs} | 21 ++++++----- 77_Salvo/csharp/Game.cs | 37 +++++-------------- .../csharp/Targetting/ComputerShotSelector.cs | 16 +++++++- .../csharp/Targetting/HumanShotSelector.cs | 2 +- 77_Salvo/csharp/Targetting/ShotSelector.cs | 4 +- 5 files changed, 38 insertions(+), 42 deletions(-) rename 77_Salvo/csharp/{Grid.cs => Fleet.cs} (75%) diff --git a/77_Salvo/csharp/Grid.cs b/77_Salvo/csharp/Fleet.cs similarity index 75% rename from 77_Salvo/csharp/Grid.cs rename to 77_Salvo/csharp/Fleet.cs index f91ae43a..5f267225 100644 --- a/77_Salvo/csharp/Grid.cs +++ b/77_Salvo/csharp/Fleet.cs @@ -3,11 +3,11 @@ using System.Diagnostics.CodeAnalysis; namespace Salvo; -internal class Grid +internal class Fleet { private readonly List _ships; - internal Grid(IReadWrite io) + internal Fleet(IReadWrite io) { io.WriteLine(Prompts.Coordinates); _ships = new() @@ -19,7 +19,7 @@ internal class Grid }; } - internal Grid(IRandom random) + internal Fleet(IRandom random) { _ships = new(); while (true) @@ -53,13 +53,14 @@ internal class Grid internal IEnumerable Ships => _ships.AsEnumerable(); - internal bool IsHit(Position position, [NotNullWhen(true)] out Ship? ship) + internal void ReceiveShots(IEnumerable shots, Action reportHit) { - ship = _ships.FirstOrDefault(s => s.IsHit(position)); - if (ship == null) { return false; } - - if (ship.IsDestroyed) { _ships.Remove(ship); } - - return true; + foreach (var position in shots) + { + var ship = _ships.FirstOrDefault(s => s.IsHit(position)); + if (ship == null) { continue; } + if (ship.IsDestroyed) { _ships.Remove(ship); } + reportHit(ship); + } } } diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index 6c0add12..5004cacb 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -20,21 +20,20 @@ internal class Game var damagedShips = new List<(int Turn, Ship Ship)>(); var temp = new Position[13]; - var computerGrid = new Grid(_random); - var humanGrid = new Grid(_io); - var humanShotSelector = new HumanShotSelector(humanGrid, _io); - var computerShotSelector = new ComputerShotSelector(computerGrid, _random); + var computerFleet = new Fleet(_random); + var humanFleet = new Fleet(_io); var startResponse = _io.ReadString(Prompts.Start); while (startResponse == Strings.WhereAreYourShips) { - foreach (var ship in computerGrid.Ships) + foreach (var ship in computerFleet.Ships) { _io.WriteLine(ship); } startResponse = _io.ReadString(Prompts.Start); } L1890: var turnNumber=0; -L1900: var seeShotsResponse = _io.ReadString(Prompts.SeeShots); + var humanShotSelector = new HumanShotSelector(humanFleet, _io); + var computerShotSelector = new ComputerShotSelector(computerFleet, _random, _io); L1920: _io.WriteLine(); L1930: if (startResponse != "YES") { goto L2620; } L1950: if (startResponse != "YES") { goto L1990; } @@ -48,13 +47,7 @@ L2230: if (humanShotSelector.CanTargetAllRemainingSquares) _io.WriteLine(Streams.YouHaveMoreShotsThanSquares); L2250: goto L2890; } - foreach (var shot1 in humanShotSelector.GetShots(turnNumber)) - { - if (computerGrid.IsHit(shot1, out var ship)) - { - _io.Write(Strings.YouHit(ship.Name)); - } - } + computerFleet.ReceiveShots(humanShotSelector.GetShots(turnNumber), ship => _io.Write(Strings.YouHit(ship.Name))); L2620: if (startResponse == "YES") { goto L2670; } L2640: turnNumber++; L2660: _io.Write(Strings.Turn(turnNumber)); @@ -68,23 +61,13 @@ L2880: if (numberOfShots > 0) { goto L2960; } L2890: _io.Write(Streams.YouWon); L2900: return; -L2960: temp = computerShotSelector.GetShots(turnNumber).ToArray(); - // display shots -L3380: if (seeShotsResponse == "YES") - { - foreach (var shot in temp) - { - _io.WriteLine(shot); - } - } - foreach (var shot in temp) - { - if (humanGrid.IsHit(shot, out var ship)) +L2960: humanFleet.ReceiveShots( + computerShotSelector.GetShots(turnNumber), + ship => { _io.Write(Strings.IHit(ship.Name)); computerShotSelector.RecordHit(ship, turnNumber); - } - } + }); goto L1950; } } diff --git a/77_Salvo/csharp/Targetting/ComputerShotSelector.cs b/77_Salvo/csharp/Targetting/ComputerShotSelector.cs index 5a5214dd..721f50c5 100644 --- a/77_Salvo/csharp/Targetting/ComputerShotSelector.cs +++ b/77_Salvo/csharp/Targetting/ComputerShotSelector.cs @@ -4,15 +4,27 @@ internal class ComputerShotSelector : ShotSelector { private readonly KnownHitsShotSelectionStrategy _knownHitsStrategy; private readonly SearchPatternShotSelectionStrategy _searchPatternStrategy; + private readonly IReadWrite _io; + private readonly bool _showShots; - internal ComputerShotSelector(Grid source, IRandom random) + internal ComputerShotSelector(Fleet source, IRandom random, IReadWrite io) : base(source) { _knownHitsStrategy = new KnownHitsShotSelectionStrategy(this); _searchPatternStrategy = new SearchPatternShotSelectionStrategy(this, random); + _io = io; + _showShots = io.ReadString(Prompts.SeeShots).Equals("yes", StringComparison.InvariantCultureIgnoreCase); } - protected override IEnumerable GetShots() => GetSelectionStrategy().GetShots(NumberOfShots); + protected override IEnumerable GetShots() + { + var shots = GetSelectionStrategy().GetShots(NumberOfShots).ToArray(); + if (_showShots) + { + _io.WriteLine(string.Join(Environment.NewLine, shots)); + } + return shots; + } internal void RecordHit(Ship ship, int turn) => _knownHitsStrategy.RecordHit(ship, turn); diff --git a/77_Salvo/csharp/Targetting/HumanShotSelector.cs b/77_Salvo/csharp/Targetting/HumanShotSelector.cs index f3e5fba2..ea49bf12 100644 --- a/77_Salvo/csharp/Targetting/HumanShotSelector.cs +++ b/77_Salvo/csharp/Targetting/HumanShotSelector.cs @@ -4,7 +4,7 @@ internal class HumanShotSelector : ShotSelector { private readonly IReadWrite _io; - internal HumanShotSelector(Grid source, IReadWrite io) + internal HumanShotSelector(Fleet source, IReadWrite io) : base(source) { _io = io; diff --git a/77_Salvo/csharp/Targetting/ShotSelector.cs b/77_Salvo/csharp/Targetting/ShotSelector.cs index 95a5c4bd..695dbc42 100644 --- a/77_Salvo/csharp/Targetting/ShotSelector.cs +++ b/77_Salvo/csharp/Targetting/ShotSelector.cs @@ -2,10 +2,10 @@ namespace Salvo.Targetting; internal abstract class ShotSelector { - private readonly Grid _source; + private readonly Fleet _source; private readonly Dictionary _previousShots = new(); - internal ShotSelector(Grid source) + internal ShotSelector(Fleet source) { _source = source; } From 461b4e2b64dd94754212c8e0b7f0992349c34b48 Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Mon, 22 May 2023 08:50:27 +1000 Subject: [PATCH 139/141] Simplify game start seelection --- 77_Salvo/csharp/Game.cs | 50 +++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index 5004cacb..dd1a49b9 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -17,28 +17,19 @@ internal class Game { _io.Write(Streams.Title); - var damagedShips = new List<(int Turn, Ship Ship)>(); - var temp = new Position[13]; - var computerFleet = new Fleet(_random); var humanFleet = new Fleet(_io); - var startResponse = _io.ReadString(Prompts.Start); - while (startResponse == Strings.WhereAreYourShips) - { - foreach (var ship in computerFleet.Ships) - { - _io.WriteLine(ship); - } - startResponse = _io.ReadString(Prompts.Start); - } + var humanStarts = AskWhoStarts(computerFleet); L1890: var turnNumber=0; var humanShotSelector = new HumanShotSelector(humanFleet, _io); var computerShotSelector = new ComputerShotSelector(computerFleet, _random, _io); L1920: _io.WriteLine(); -L1930: if (startResponse != "YES") { goto L2620; } -L1950: if (startResponse != "YES") { goto L1990; } -L1960: turnNumber++; -L1980: _io.Write(Strings.Turn(turnNumber)); +L1930: if (!humanStarts) { goto L2620; } +L1950: if (humanStarts) + { +L1960: turnNumber++; +L1980: _io.Write(Strings.Turn(turnNumber)); + } L1990: var numberOfShots = humanShotSelector.NumberOfShots; L2220: _io.Write(Strings.YouHaveShots(numberOfShots)); if (numberOfShots == 0) { goto L2270; } @@ -48,9 +39,11 @@ L2230: if (humanShotSelector.CanTargetAllRemainingSquares) L2250: goto L2890; } computerFleet.ReceiveShots(humanShotSelector.GetShots(turnNumber), ship => _io.Write(Strings.YouHit(ship.Name))); -L2620: if (startResponse == "YES") { goto L2670; } -L2640: turnNumber++; -L2660: _io.Write(Strings.Turn(turnNumber)); +L2620: if (!humanStarts) + { +L2640: turnNumber++; +L2660: _io.Write(Strings.Turn(turnNumber)); + } L2670: numberOfShots = computerShotSelector.NumberOfShots; L2840: _io.Write(Strings.IHaveShots(numberOfShots)); L2850: if (!computerShotSelector.CanTargetAllRemainingSquares) { goto L2880; } @@ -70,6 +63,25 @@ L2960: humanFleet.ReceiveShots( }); goto L1950; } + + private bool AskWhoStarts(Fleet computerFleet) + { + while (true) + { + var startResponse = _io.ReadString(Prompts.Start); + if (startResponse.Equals(Strings.WhereAreYourShips, StringComparison.InvariantCultureIgnoreCase)) + { + foreach (var ship in computerFleet.Ships) + { + _io.WriteLine(ship); + } + } + else + { + return startResponse.Equals("yes", StringComparison.InvariantCultureIgnoreCase); + } + } + } } internal class DataRandom : IRandom From d1be351045e2b3e0288935252d6a42492053b8be Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Tue, 23 May 2023 07:55:41 +1000 Subject: [PATCH 140/141] Simplify game loop --- 77_Salvo/csharp/Game.cs | 11 ++++++++++- .../csharp/Resources/YouHaveMoreShotsThanSquares.txt | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index dd1a49b9..ff43c268 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -38,7 +38,9 @@ L2230: if (humanShotSelector.CanTargetAllRemainingSquares) _io.WriteLine(Streams.YouHaveMoreShotsThanSquares); L2250: goto L2890; } - computerFleet.ReceiveShots(humanShotSelector.GetShots(turnNumber), ship => _io.Write(Strings.YouHit(ship.Name))); + computerFleet.ReceiveShots( + humanShotSelector.GetShots(turnNumber), + ship => _io.Write(Strings.YouHit(ship.Name))); L2620: if (!humanStarts) { L2640: turnNumber++; @@ -84,6 +86,13 @@ L2960: humanFleet.ReceiveShots( } } +internal enum Winner +{ + None, + Human, + Computer +} + internal class DataRandom : IRandom { private readonly Queue _values = diff --git a/77_Salvo/csharp/Resources/YouHaveMoreShotsThanSquares.txt b/77_Salvo/csharp/Resources/YouHaveMoreShotsThanSquares.txt index 37c06812..04d58f92 100644 --- a/77_Salvo/csharp/Resources/YouHaveMoreShotsThanSquares.txt +++ b/77_Salvo/csharp/Resources/YouHaveMoreShotsThanSquares.txt @@ -1 +1 @@ -You have more shots that there are blank squares. +You have more shots than there are blank squares. From 2e52c6e1878a4d63e20c590c6d494b6ac06009bb Mon Sep 17 00:00:00 2001 From: drewjcooper Date: Wed, 24 May 2023 20:58:37 +1000 Subject: [PATCH 141/141] Add TurnHandler --- 77_Salvo/csharp/Game.cs | 98 +++------------------------------- 77_Salvo/csharp/TurnHandler.cs | 92 +++++++++++++++++++++++++++++++ 77_Salvo/csharp/Winner.cs | 7 +++ 3 files changed, 107 insertions(+), 90 deletions(-) create mode 100644 77_Salvo/csharp/TurnHandler.cs create mode 100644 77_Salvo/csharp/Winner.cs diff --git a/77_Salvo/csharp/Game.cs b/77_Salvo/csharp/Game.cs index ff43c268..a0223d8b 100644 --- a/77_Salvo/csharp/Game.cs +++ b/77_Salvo/csharp/Game.cs @@ -1,5 +1,3 @@ -using Salvo.Targetting; - namespace Salvo; internal class Game @@ -17,95 +15,15 @@ internal class Game { _io.Write(Streams.Title); - var computerFleet = new Fleet(_random); - var humanFleet = new Fleet(_io); - var humanStarts = AskWhoStarts(computerFleet); -L1890: var turnNumber=0; - var humanShotSelector = new HumanShotSelector(humanFleet, _io); - var computerShotSelector = new ComputerShotSelector(computerFleet, _random, _io); -L1920: _io.WriteLine(); -L1930: if (!humanStarts) { goto L2620; } -L1950: if (humanStarts) - { -L1960: turnNumber++; -L1980: _io.Write(Strings.Turn(turnNumber)); - } -L1990: var numberOfShots = humanShotSelector.NumberOfShots; -L2220: _io.Write(Strings.YouHaveShots(numberOfShots)); - if (numberOfShots == 0) { goto L2270; } -L2230: if (humanShotSelector.CanTargetAllRemainingSquares) - { - _io.WriteLine(Streams.YouHaveMoreShotsThanSquares); -L2250: goto L2890; - } - computerFleet.ReceiveShots( - humanShotSelector.GetShots(turnNumber), - ship => _io.Write(Strings.YouHit(ship.Name))); -L2620: if (!humanStarts) - { -L2640: turnNumber++; -L2660: _io.Write(Strings.Turn(turnNumber)); - } -L2670: numberOfShots = computerShotSelector.NumberOfShots; -L2840: _io.Write(Strings.IHaveShots(numberOfShots)); -L2850: if (!computerShotSelector.CanTargetAllRemainingSquares) { goto L2880; } -L2860: _io.Write(Streams.IHaveMoreShotsThanSquares); -L2270: _io.Write(Streams.IWon); - return; -L2880: if (numberOfShots > 0) { goto L2960; } -L2890: _io.Write(Streams.YouWon); -L2900: return; + var turnHandler = new TurnHandler(_io, _random); + _io.WriteLine(); -L2960: humanFleet.ReceiveShots( - computerShotSelector.GetShots(turnNumber), - ship => - { - _io.Write(Strings.IHit(ship.Name)); - computerShotSelector.RecordHit(ship, turnNumber); - }); - goto L1950; - } - - private bool AskWhoStarts(Fleet computerFleet) - { - while (true) + Winner? winner; + do { - var startResponse = _io.ReadString(Prompts.Start); - if (startResponse.Equals(Strings.WhereAreYourShips, StringComparison.InvariantCultureIgnoreCase)) - { - foreach (var ship in computerFleet.Ships) - { - _io.WriteLine(ship); - } - } - else - { - return startResponse.Equals("yes", StringComparison.InvariantCultureIgnoreCase); - } - } + winner = turnHandler.PlayTurn(); + } while (winner == null); + + _io.Write(winner == Winner.Computer ? Streams.IWon : Streams.YouWon); } } - -internal enum Winner -{ - None, - Human, - Computer -} - -internal class DataRandom : IRandom -{ - private readonly Queue _values = - new(File.ReadAllLines("data.txt").Select(l => float.Parse(l) / 1000000)); - private float _previous; - - public float NextFloat() => _previous = _values.Dequeue(); - - public float PreviousFloat() => _previous; - - public void Reseed(int seed) - { - throw new NotImplementedException(); - } -} - diff --git a/77_Salvo/csharp/TurnHandler.cs b/77_Salvo/csharp/TurnHandler.cs new file mode 100644 index 00000000..afde9613 --- /dev/null +++ b/77_Salvo/csharp/TurnHandler.cs @@ -0,0 +1,92 @@ +using Salvo.Targetting; + +namespace Salvo; + +internal class TurnHandler +{ + private readonly IReadWrite _io; + private readonly Fleet _humanFleet; + private readonly Fleet _computerFleet; + private readonly bool _humanStarts; + private readonly HumanShotSelector _humanShotSelector; + private readonly ComputerShotSelector _computerShotSelector; + private readonly Func _turnAction; + private int _turnNumber; + + public TurnHandler(IReadWrite io, IRandom random) + { + _io = io; + _computerFleet = new Fleet(random); + _humanFleet = new Fleet(io); + _turnAction = AskWhoStarts() + ? () => PlayHumanTurn() ?? PlayComputerTurn() + : () => PlayComputerTurn() ?? PlayHumanTurn(); + _humanShotSelector = new HumanShotSelector(_humanFleet, io); + _computerShotSelector = new ComputerShotSelector(_computerFleet, random, io); + } + + public Winner? PlayTurn() + { + _io.Write(Strings.Turn(++_turnNumber)); + return _turnAction.Invoke(); + } + + private bool AskWhoStarts() + { + while (true) + { + var startResponse = _io.ReadString(Prompts.Start); + if (startResponse.Equals(Strings.WhereAreYourShips, StringComparison.InvariantCultureIgnoreCase)) + { + foreach (var ship in _computerFleet.Ships) + { + _io.WriteLine(ship); + } + } + else + { + return startResponse.Equals("yes", StringComparison.InvariantCultureIgnoreCase); + } + } + } + + private Winner? PlayComputerTurn() + { + var numberOfShots = _computerShotSelector.NumberOfShots; + _io.Write(Strings.IHaveShots(numberOfShots)); + if (numberOfShots == 0) { return Winner.Human; } + if (_computerShotSelector.CanTargetAllRemainingSquares) + { + _io.Write(Streams.IHaveMoreShotsThanSquares); + return Winner.Computer; + } + + _humanFleet.ReceiveShots( + _computerShotSelector.GetShots(_turnNumber), + ship => + { + _io.Write(Strings.IHit(ship.Name)); + _computerShotSelector.RecordHit(ship, _turnNumber); + }); + + return null; + } + + private Winner? PlayHumanTurn() + { + var numberOfShots = _humanShotSelector.NumberOfShots; + _io.Write(Strings.YouHaveShots(numberOfShots)); + if (numberOfShots == 0) { return Winner.Computer; } + if (_humanShotSelector.CanTargetAllRemainingSquares) + { + _io.WriteLine(Streams.YouHaveMoreShotsThanSquares); + return Winner.Human; + } + + _computerFleet.ReceiveShots( + _humanShotSelector.GetShots(_turnNumber), + ship => _io.Write(Strings.YouHit(ship.Name))); + + return null; + } +} diff --git a/77_Salvo/csharp/Winner.cs b/77_Salvo/csharp/Winner.cs new file mode 100644 index 00000000..a5a73eb4 --- /dev/null +++ b/77_Salvo/csharp/Winner.cs @@ -0,0 +1,7 @@ +namespace Salvo; + +internal enum Winner +{ + Human, + Computer +}