diff --git a/04_Awari/rust/Cargo.toml b/04_Awari/rust/Cargo.toml new file mode 100644 index 00000000..1c2935d4 --- /dev/null +++ b/04_Awari/rust/Cargo.toml @@ -0,0 +1,9 @@ +[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] +rand = "0.8.5" \ No newline at end of file diff --git a/04_Awari/rust/src/main.rs b/04_Awari/rust/src/main.rs new file mode 100644 index 00000000..7aa00831 --- /dev/null +++ b/04_Awari/rust/src/main.rs @@ -0,0 +1,214 @@ +use std::{thread, time::Duration}; + +use rand::Rng; + +// AI "learning" is not implemented. Don't have the time. - Ugur + +fn main() { + loop { + let mut game = Game::default(); + + loop { + game.draw(); + if game.play_turn(false) { + break; + } + } + } +} + +enum DistributeResult { + Normal, + // Leftover beans + EndOnHomePit(bool), + // "true" if ended on Player Home Pit + EndOnEmptyPit(usize), + // "index" of the empty pit within the Row + ChosenEmpty, +} + +struct Game { + pits: [u8; 14], + player_turn: bool, +} + +impl Default for Game { + fn default() -> Self { + println!("\n\n\t\t AWARI"); + println!("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"); + + Self { + pits: [3, 3, 3, 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 0], + player_turn: true, + } + } +} + +impl Game { + fn step_through(&mut self, mut index: usize) -> usize { + let mut bean_amount = self.pits[index]; + self.pits[index] = 0; + + loop { + index += 1; + + if index > self.pits.len() - 1 { + index = 0; + } + + self.pits[index] += 1; + + bean_amount -= 1; + if bean_amount == 0 { + return index; + } + } + } + + fn play_turn(&mut self, is_repeat: bool) -> bool { + use DistributeResult::*; + + if self.is_game_over() { + println!("\nGame Over!"); + let (player_beans, ai_beans) = (self.pits[6], self.pits[13]); + if player_beans == ai_beans { + println!("It's a draw") + } else if player_beans > ai_beans { + println!("You win by {}", player_beans - ai_beans); + } else { + println!("I win by {}", ai_beans - player_beans); + } + return true; + } + + let chosen_index = if self.player_turn { + player_prompt(if is_repeat { "Again?" } else { "Your move?" }) - 1 + } else { + println!("========================"); + + thread::sleep(Duration::from_secs(1)); + + let non_empty_pits: Vec = self + .pits + .iter() + .enumerate() + .filter(|&(i, p)| (7..13).contains(&i) && *p > 0) + .map(|(i, _)| i) + .collect(); + let random_index = rand::thread_rng().gen_range(0..non_empty_pits.len()); + let ai_move = non_empty_pits[random_index]; + + println!("My move is {}", ai_move - 6); + + println!("========================"); + ai_move + }; + + match self.process_choice(chosen_index) { + Normal => (), + EndOnHomePit(player) => { + self.draw(); + + if player == self.player_turn && !is_repeat { + self.play_turn(true); + } + } + EndOnEmptyPit(last_index) => { + let opposite_index = 12 - last_index; + let home_index = if self.player_turn { 6 } else { 13 }; + let won_beans = 1 + self.pits[opposite_index]; + + self.pits[last_index] = 0; + self.pits[opposite_index] = 0; + self.pits[home_index] += won_beans; + } + ChosenEmpty => { + println!("Chosen pit is empty"); + return self.play_turn(is_repeat); + } + } + + if !is_repeat { + self.player_turn = !self.player_turn; + } + + false + } + + pub fn process_choice(&mut self, index: usize) -> DistributeResult { + use DistributeResult::*; + + if self.pits[index] == 0 { + return ChosenEmpty; + } + + let last_index = self.step_through(index); + + if last_index == 6 && self.player_turn { + return EndOnHomePit(true); + } else if last_index == 13 && !self.player_turn { + return EndOnHomePit(false); + } else if self.pits[last_index] == 1 { + return EndOnEmptyPit(last_index); + } + + Normal + } + + fn is_game_over(&self) -> bool { + let player_empty = !(0..6).any(|i| self.pits[i] > 0); + let ai_empty = !(7..13).any(|i| self.pits[i] > 0); + player_empty || ai_empty + } + + fn draw(&self) { + let row_as_string = |player: bool| -> String { + let mut row_as_string = String::new(); + + let range = if player { 0..6 } else { 7..13 }; + + range.for_each(|i| { + let mut bean_amount_as_string = self.pits[i].to_string(); + bean_amount_as_string.push_str(" "); + + if player { + row_as_string.push_str(&bean_amount_as_string); + } else { + row_as_string.insert_str(0, &bean_amount_as_string); + } + }); + + row_as_string + }; + + println!( + "\n {}\n{} {}\n {}\n", + row_as_string(false), + self.pits[13].to_string(), + self.pits[6].to_string(), + row_as_string(true) + ); + } +} + +pub fn player_prompt(message: &str) -> usize { + loop { + let mut input = String::new(); + println!("{}", message); + + if let Ok(_) = std::io::stdin().read_line(&mut input) { + match input.trim().parse::() { + Ok(n) => { + if (1..=6).contains(&n) { + return n; + } else { + println!("Enter a number between 1 and 6") + } + } + Err(e) => { + println!("{}", e); + } + } + } + } +} diff --git a/16_Bug/csharp/Bug.cs b/16_Bug/csharp/Bug.cs new file mode 100644 index 00000000..42e686d4 --- /dev/null +++ b/16_Bug/csharp/Bug.cs @@ -0,0 +1,21 @@ +using System.Text; +using BugGame.Parts; +using BugGame.Resources; + +namespace BugGame; + +internal class Bug +{ + private readonly Body _body = new(); + + public bool IsComplete => _body.IsComplete; + + public bool TryAdd(IPart part, out Message message) => _body.TryAdd(part, out message); + + public string ToString(string pronoun, char feelerCharacter) + { + var builder = new StringBuilder($"*****{pronoun} Bug*****").AppendLine().AppendLine().AppendLine(); + _body.AppendTo(builder, feelerCharacter); + return builder.ToString(); + } +} \ No newline at end of file diff --git a/16_Bug/csharp/Bug.csproj b/16_Bug/csharp/Bug.csproj index d3fe4757..91e759c0 100644 --- a/16_Bug/csharp/Bug.csproj +++ b/16_Bug/csharp/Bug.csproj @@ -6,4 +6,13 @@ enable enable + + + + + + + + + diff --git a/16_Bug/csharp/Game.cs b/16_Bug/csharp/Game.cs new file mode 100644 index 00000000..a8989a4c --- /dev/null +++ b/16_Bug/csharp/Game.cs @@ -0,0 +1,86 @@ +using BugGame.Parts; +using BugGame.Resources; +using Games.Common.IO; +using Games.Common.Randomness; +using static System.StringComparison; +namespace BugGame; + +internal class Game +{ + private readonly IReadWrite _io; + private readonly IRandom _random; + + public Game(IReadWrite io, IRandom random) + { + _io = io; + _random = random; + } + + public void Play() + { + _io.Write(Resource.Streams.Introduction); + if (!_io.ReadString("Do you want instructions").Equals("no", InvariantCultureIgnoreCase)) + { + _io.Write(Resource.Streams.Instructions); + } + + BuildBugs(); + + _io.Write(Resource.Streams.PlayAgain); + } + + private void BuildBugs() + { + var yourBug = new Bug(); + var myBug = new Bug(); + + while (true) + { + var partAdded = TryBuild(yourBug, m => m.You); + Thread.Sleep(500); + _io.WriteLine(); + partAdded |= TryBuild(myBug, m => m.I); + + if (partAdded) + { + if (yourBug.IsComplete) { _io.WriteLine("Your bug is finished."); } + if (myBug.IsComplete) { _io.WriteLine("My bug is finished."); } + + if (!_io.ReadString("Do you want the picture").Equals("no", InvariantCultureIgnoreCase)) + { + _io.Write(yourBug.ToString("Your", 'A')); + _io.WriteLine(); + _io.WriteLine(); + _io.WriteLine(); + _io.WriteLine(); + _io.Write(myBug.ToString("My", 'F')); + } + } + + if (yourBug.IsComplete || myBug.IsComplete) { break; } + } + } + + private bool TryBuild(Bug bug, Func messageTransform) + { + var roll = _random.Next(6) + 1; + _io.WriteLine(messageTransform(Message.Rolled.ForValue(roll))); + + IPart part = roll switch + { + 1 => new Body(), + 2 => new Neck(), + 3 => new Head(), + 4 => new Feeler(), + 5 => new Tail(), + 6 => new Leg(), + _ => throw new Exception("Unexpected roll value") + }; + _io.WriteLine($"{roll}={part.GetType().Name}"); + + var partAdded = bug.TryAdd(part, out var message); + _io.WriteLine(messageTransform.Invoke(message)); + + return partAdded; + } +} \ No newline at end of file diff --git a/16_Bug/csharp/Parts/Body.cs b/16_Bug/csharp/Parts/Body.cs new file mode 100644 index 00000000..58f80405 --- /dev/null +++ b/16_Bug/csharp/Parts/Body.cs @@ -0,0 +1,44 @@ +using System.Text; +using BugGame.Resources; + +namespace BugGame.Parts; + +internal class Body : ParentPart +{ + private readonly Neck _neck = new(); + private readonly Tail _tail = new(); + private readonly Legs _legs = new(); + + public Body() + : base(Message.BodyAdded, Message.BodyNotNeeded) + { + } + + public override bool IsComplete => _neck.IsComplete && _tail.IsComplete && _legs.IsComplete; + + protected override bool TryAddCore(IPart part, out Message message) + => part switch + { + Neck => _neck.TryAdd(out message), + Head or Feeler => _neck.TryAdd(part, out message), + Tail => _tail.TryAdd(out message), + Leg => _legs.TryAddOne(out message), + _ => throw new NotSupportedException($"Can't add a {part.Name} to a {Name}.") + }; + + public void AppendTo(StringBuilder builder, char feelerCharacter) + { + if (IsPresent) + { + _neck.AppendTo(builder, feelerCharacter); + builder + .AppendLine(" BBBBBBBBBBBB") + .AppendLine(" B B") + .AppendLine(" B B"); + _tail.AppendTo(builder); + builder + .AppendLine(" BBBBBBBBBBBB"); + _legs.AppendTo(builder); + } + } +} diff --git a/16_Bug/csharp/Parts/Feeler.cs b/16_Bug/csharp/Parts/Feeler.cs new file mode 100644 index 00000000..9508d541 --- /dev/null +++ b/16_Bug/csharp/Parts/Feeler.cs @@ -0,0 +1,6 @@ +namespace BugGame.Parts; + +internal class Feeler : IPart +{ + public string Name => nameof(Feeler); +} diff --git a/16_Bug/csharp/Parts/Feelers.cs b/16_Bug/csharp/Parts/Feelers.cs new file mode 100644 index 00000000..165a073d --- /dev/null +++ b/16_Bug/csharp/Parts/Feelers.cs @@ -0,0 +1,14 @@ +using System.Text; +using BugGame.Resources; + +namespace BugGame.Parts; + +internal class Feelers : PartCollection +{ + public Feelers() + : base(2, Message.FeelerAdded, Message.FeelersFull) + { + } + + public void AppendTo(StringBuilder builder, char character) => AppendTo(builder, 10, 4, character); +} diff --git a/16_Bug/csharp/Parts/Head.cs b/16_Bug/csharp/Parts/Head.cs new file mode 100644 index 00000000..d7135575 --- /dev/null +++ b/16_Bug/csharp/Parts/Head.cs @@ -0,0 +1,38 @@ +using System.Text; +using BugGame.Resources; + +namespace BugGame.Parts; + +internal class Head : ParentPart +{ + private Feelers _feelers = new(); + + public Head() + : base(Message.HeadAdded, Message.HeadNotNeeded) + { + } + + public override bool IsComplete => _feelers.IsComplete; + + protected override bool TryAddCore(IPart part, out Message message) + => part switch + { + Feeler => _feelers.TryAddOne(out message), + _ => throw new NotSupportedException($"Can't add a {part.Name} to a {Name}.") + }; + + public void AppendTo(StringBuilder builder, char feelerCharacter) + { + if (IsPresent) + { + _feelers.AppendTo(builder, feelerCharacter); + builder + .AppendLine(" HHHHHHH") + .AppendLine(" H H") + .AppendLine(" H O O H") + .AppendLine(" H H") + .AppendLine(" H V H") + .AppendLine(" HHHHHHH"); + } + } +} diff --git a/16_Bug/csharp/Parts/IPart.cs b/16_Bug/csharp/Parts/IPart.cs new file mode 100644 index 00000000..e325a7c1 --- /dev/null +++ b/16_Bug/csharp/Parts/IPart.cs @@ -0,0 +1,6 @@ +namespace BugGame.Parts; + +internal interface IPart +{ + string Name { get; } +} diff --git a/16_Bug/csharp/Parts/Leg.cs b/16_Bug/csharp/Parts/Leg.cs new file mode 100644 index 00000000..c2d4aaaf --- /dev/null +++ b/16_Bug/csharp/Parts/Leg.cs @@ -0,0 +1,6 @@ +namespace BugGame.Parts; + +internal class Leg : IPart +{ + public string Name => nameof(Leg); +} diff --git a/16_Bug/csharp/Parts/Legs.cs b/16_Bug/csharp/Parts/Legs.cs new file mode 100644 index 00000000..0ed8d8fc --- /dev/null +++ b/16_Bug/csharp/Parts/Legs.cs @@ -0,0 +1,14 @@ +using System.Text; +using BugGame.Resources; + +namespace BugGame.Parts; + +internal class Legs : PartCollection +{ + public Legs() + : base(6, Message.LegAdded, Message.LegsFull) + { + } + + public void AppendTo(StringBuilder builder) => AppendTo(builder, 6, 2, 'L'); +} diff --git a/16_Bug/csharp/Parts/Neck.cs b/16_Bug/csharp/Parts/Neck.cs new file mode 100644 index 00000000..23dacfb7 --- /dev/null +++ b/16_Bug/csharp/Parts/Neck.cs @@ -0,0 +1,33 @@ +using System.Text; +using BugGame.Resources; + +namespace BugGame.Parts; + +internal class Neck : ParentPart +{ + private Head _head = new(); + + public Neck() + : base(Message.NeckAdded, Message.NeckNotNeeded) + { + } + + public override bool IsComplete => _head.IsComplete; + + protected override bool TryAddCore(IPart part, out Message message) + => part switch + { + Head => _head.TryAdd(out message), + Feeler => _head.TryAdd(part, out message), + _ => throw new NotSupportedException($"Can't add a {part.Name} to a {Name}.") + }; + + public void AppendTo(StringBuilder builder, char feelerCharacter) + { + if (IsPresent) + { + _head.AppendTo(builder, feelerCharacter); + builder.AppendLine(" N N").AppendLine(" N N"); + } + } +} diff --git a/16_Bug/csharp/Parts/ParentPart.cs b/16_Bug/csharp/Parts/ParentPart.cs new file mode 100644 index 00000000..1ca6682f --- /dev/null +++ b/16_Bug/csharp/Parts/ParentPart.cs @@ -0,0 +1,27 @@ +using BugGame.Resources; + +namespace BugGame.Parts; + +internal abstract class ParentPart : Part +{ + public ParentPart(Message addedMessage, Message duplicateMessage) + : base(addedMessage, duplicateMessage) + { + } + + public bool TryAdd(IPart part, out Message message) + => (part.GetType() == GetType(), IsPresent) switch + { + (true, _) => TryAdd(out message), + (false, false) => ReportDoNotHave(out message), + _ => TryAddCore(part, out message) + }; + + protected abstract bool TryAddCore(IPart part, out Message message); + + private bool ReportDoNotHave(out Message message) + { + message = Message.DoNotHaveA(this); + return false; + } +} diff --git a/16_Bug/csharp/Parts/Part.cs b/16_Bug/csharp/Parts/Part.cs new file mode 100644 index 00000000..f29fbd81 --- /dev/null +++ b/16_Bug/csharp/Parts/Part.cs @@ -0,0 +1,34 @@ +using BugGame.Resources; + +namespace BugGame.Parts; + +internal class Part : IPart +{ + private readonly Message _addedMessage; + private readonly Message _duplicateMessage; + + public Part(Message addedMessage, Message duplicateMessage) + { + _addedMessage = addedMessage; + _duplicateMessage = duplicateMessage; + } + + public virtual bool IsComplete => IsPresent; + + protected bool IsPresent { get; private set; } + + public string Name => GetType().Name; + + public bool TryAdd(out Message message) + { + if (IsPresent) + { + message = _duplicateMessage; + return false; + } + + message = _addedMessage; + IsPresent = true; + return true; + } +} diff --git a/16_Bug/csharp/Parts/PartCollection.cs b/16_Bug/csharp/Parts/PartCollection.cs new file mode 100644 index 00000000..9a0fd2ee --- /dev/null +++ b/16_Bug/csharp/Parts/PartCollection.cs @@ -0,0 +1,50 @@ +using System.Text; +using BugGame.Resources; + +namespace BugGame.Parts; + +internal class PartCollection +{ + private readonly int _maxCount; + private readonly Message _addedMessage; + private readonly Message _fullMessage; + private int _count; + + public PartCollection(int maxCount, Message addedMessage, Message fullMessage) + { + _maxCount = maxCount; + _addedMessage = addedMessage; + _fullMessage = fullMessage; + } + + public bool IsComplete => _count == _maxCount; + + public bool TryAddOne(out Message message) + { + if (_count < _maxCount) + { + _count++; + message = _addedMessage.ForValue(_count); + return true; + } + + message = _fullMessage; + return false; + } + + protected void AppendTo(StringBuilder builder, int offset, int length, char character) + { + if (_count == 0) { return; } + + for (var i = 0; i < length; i++) + { + builder.Append(' ', offset); + + for (var j = 0; j < _count; j++) + { + builder.Append(character).Append(' '); + } + builder.AppendLine(); + } + } +} diff --git a/16_Bug/csharp/Parts/Tail.cs b/16_Bug/csharp/Parts/Tail.cs new file mode 100644 index 00000000..ebf4b28f --- /dev/null +++ b/16_Bug/csharp/Parts/Tail.cs @@ -0,0 +1,20 @@ +using System.Text; +using BugGame.Resources; + +namespace BugGame.Parts; + +internal class Tail : Part +{ + public Tail() + : base(Message.TailAdded, Message.TailNotNeeded) + { + } + + public void AppendTo(StringBuilder builder) + { + if (IsPresent) + { + builder.AppendLine("TTTTTB B"); + } + } +} \ No newline at end of file diff --git a/16_Bug/csharp/Program.cs b/16_Bug/csharp/Program.cs new file mode 100644 index 00000000..883e62d1 --- /dev/null +++ b/16_Bug/csharp/Program.cs @@ -0,0 +1,5 @@ +using BugGame; +using Games.Common.IO; +using Games.Common.Randomness; + +new Game(new ConsoleIO(), new RandomNumberGenerator()).Play(); diff --git a/16_Bug/csharp/Resources/Instructions.txt b/16_Bug/csharp/Resources/Instructions.txt new file mode 100644 index 00000000..bdbdadd3 --- /dev/null +++ b/16_Bug/csharp/Resources/Instructions.txt @@ -0,0 +1,18 @@ +The object of Bug is to finish your bug before I finish +mine. Each number stands for a part of the bug body. +I will roll the die for you, tell you what I rolled for you +what the number stands for, and if you can get the part. +If you can get the part I will give it to you. +The same will happen on my turn. +If there is a change in either bug I will give you the +option of seeing the pictures of the bugs. +The numbers stand for parts as follows: +Number Part Number of part needed + 1 Body 1 + 2 Neck 1 + 3 Head 1 + 4 Feelers 2 + 5 Tail 1 + 6 Legs 6 + + diff --git a/16_Bug/csharp/Resources/Introduction.txt b/16_Bug/csharp/Resources/Introduction.txt new file mode 100644 index 00000000..e74a833e --- /dev/null +++ b/16_Bug/csharp/Resources/Introduction.txt @@ -0,0 +1,8 @@ + Bug + Creative Computing Morristown, New Jersey + + + +The Game Bug +I hope you enjoy this game. + diff --git a/16_Bug/csharp/Resources/Message.cs b/16_Bug/csharp/Resources/Message.cs new file mode 100644 index 00000000..20a59d9a --- /dev/null +++ b/16_Bug/csharp/Resources/Message.cs @@ -0,0 +1,46 @@ +using BugGame.Parts; + +namespace BugGame.Resources; + +internal class Message +{ + public static Message Rolled = new("rolled a {0}"); + + public static Message BodyAdded = new("now have a body."); + public static Message BodyNotNeeded = new("do not need a body."); + + public static Message NeckAdded = new("now have a neck."); + public static Message NeckNotNeeded = new("do not need a neck."); + + public static Message HeadAdded = new("needed a head."); + public static Message HeadNotNeeded = new("I do not need a head.", "You have a head."); + + public static Message TailAdded = new("I now have a tail.", "I now give you a tail."); + public static Message TailNotNeeded = new("I do not need a tail.", "You already have a tail."); + + public static Message FeelerAdded = new("I get a feeler.", "I now give you a feeler"); + public static Message FeelersFull = new("I have 2 feelers already.", "You have two feelers already"); + + public static Message LegAdded = new("now have {0} legs"); + public static Message LegsFull = new("I have 6 feet.", "You have 6 feet already"); + + public static Message Complete = new("bug is finished."); + + private Message(string common) + : this("I " + common, "You " + common) + { + } + + private Message(string i, string you) + { + I = i; + You = you; + } + + public string I { get; } + public string You { get; } + + public static Message DoNotHaveA(Part part) => new($"do not have a {part.Name}"); + + public Message ForValue(int quantity) => new(string.Format(I, quantity), string.Format(You, quantity)); +} \ No newline at end of file diff --git a/16_Bug/csharp/Resources/PlayAgain.txt b/16_Bug/csharp/Resources/PlayAgain.txt new file mode 100644 index 00000000..2380e130 --- /dev/null +++ b/16_Bug/csharp/Resources/PlayAgain.txt @@ -0,0 +1 @@ +I hope you enjoyed the game, play it again soon!! diff --git a/16_Bug/csharp/Resources/Resource.cs b/16_Bug/csharp/Resources/Resource.cs new file mode 100644 index 00000000..2b34a4e6 --- /dev/null +++ b/16_Bug/csharp/Resources/Resource.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace BugGame.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream Introduction => GetStream(); + public static Stream Instructions => GetStream(); + public static Stream PlayAgain => GetStream(); + } + + private static Stream GetStream([CallerMemberName] string? name = null) => + Assembly.GetExecutingAssembly() + .GetManifestResourceStream($"Bug.Resources.{name}.txt") + ?? throw new Exception($"Could not find embedded resource stream '{name}'."); +} \ No newline at end of file diff --git a/26_Chomp/csharp/Chomp.csproj b/26_Chomp/csharp/Chomp.csproj index d3fe4757..3870320c 100644 --- a/26_Chomp/csharp/Chomp.csproj +++ b/26_Chomp/csharp/Chomp.csproj @@ -6,4 +6,12 @@ enable enable + + + + + + + + diff --git a/26_Chomp/csharp/Cookie.cs b/26_Chomp/csharp/Cookie.cs new file mode 100644 index 00000000..725eadfe --- /dev/null +++ b/26_Chomp/csharp/Cookie.cs @@ -0,0 +1,56 @@ +using System.Text; + +namespace Chomp; + +internal class Cookie +{ + private readonly int _rowCount; + private readonly int _columnCount; + private readonly char[][] _bits; + + public Cookie(int rowCount, int columnCount) + { + _rowCount = rowCount; + _columnCount = columnCount; + + // The calls to Math.Max here are to duplicate the original behaviour + // when negative values are given for the row or column count. + _bits = new char[Math.Max(_rowCount, 1)][]; + for (int row = 0; row < _bits.Length; row++) + { + _bits[row] = Enumerable.Repeat('*', Math.Max(_columnCount, 1)).ToArray(); + } + _bits[0][0] = 'P'; + } + + public bool TryChomp(int row, int column, out char chomped) + { + if (row < 1 || row > _rowCount || column < 1 || column > _columnCount || _bits[row - 1][column - 1] == ' ') + { + chomped = default; + return false; + } + + chomped = _bits[row - 1][column - 1]; + + for (int r = row; r <= _rowCount; r++) + { + for (int c = column; c <= _columnCount; c++) + { + _bits[r - 1][c - 1] = ' '; + } + } + + return true; + } + + public override string ToString() + { + var builder = new StringBuilder().AppendLine(" 1 2 3 4 5 6 7 8 9"); + for (int row = 1; row <= _bits.Length; row++) + { + builder.Append(' ').Append(row).Append(" ").AppendLine(string.Join(' ', _bits[row - 1])); + } + return builder.ToString(); + } +} \ No newline at end of file diff --git a/26_Chomp/csharp/Game.cs b/26_Chomp/csharp/Game.cs new file mode 100644 index 00000000..77a8ccd1 --- /dev/null +++ b/26_Chomp/csharp/Game.cs @@ -0,0 +1,64 @@ +namespace Chomp; + +internal class Game +{ + private readonly IReadWrite _io; + + public Game(IReadWrite io) + { + _io = io; + } + + internal void Play() + { + _io.Write(Resource.Streams.Introduction); + if (_io.ReadNumber("Do you want the rules (1=Yes, 0=No!)") != 0) + { + _io.Write(Resource.Streams.Rules); + } + + while (true) + { + _io.Write(Resource.Streams.HereWeGo); + + var (playerCount, rowCount, columnCount) = _io.ReadParameters(); + + var loser = Play(new Cookie(rowCount, columnCount), new PlayerNumber(playerCount)); + + _io.WriteLine(string.Format(Resource.Formats.YouLose, loser)); + + if (_io.ReadNumber("Again (1=Yes, 0=No!)") != 1) { break; } + } + } + + private PlayerNumber Play(Cookie cookie, PlayerNumber player) + { + while (true) + { + _io.WriteLine(cookie); + + var poisoned = Chomp(cookie, player); + + if (poisoned) { return player; } + + player++; + } + } + + private bool Chomp(Cookie cookie, PlayerNumber player) + { + while (true) + { + _io.WriteLine(string.Format(Resource.Formats.Player, player)); + + var (row, column) = _io.Read2Numbers(Resource.Prompts.Coordinates); + + if (cookie.TryChomp((int)row, (int)column, out char chomped)) + { + return chomped == 'P'; + } + + _io.Write(Resource.Streams.NoFair); + } + } +} diff --git a/26_Chomp/csharp/IOExtensions.cs b/26_Chomp/csharp/IOExtensions.cs new file mode 100644 index 00000000..c94053a9 --- /dev/null +++ b/26_Chomp/csharp/IOExtensions.cs @@ -0,0 +1,24 @@ +namespace Chomp; + +internal static class IOExtensions +{ + public static (float, int, int) ReadParameters(this IReadWrite io) + => ( + (int)io.ReadNumber(Resource.Prompts.HowManyPlayers), + io.ReadNumberWithMax(Resource.Prompts.HowManyRows, 9, Resource.Strings.TooManyRows), + io.ReadNumberWithMax(Resource.Prompts.HowManyColumns, 9, Resource.Strings.TooManyColumns) + ); + + private static int ReadNumberWithMax(this IReadWrite io, string initialPrompt, int max, string reprompt) + { + var prompt = initialPrompt; + + while (true) + { + var response = io.ReadNumber(prompt); + if (response <= 9) { return (int)response; } + + prompt = $"{reprompt} {initialPrompt.ToLowerInvariant()}"; + } + } +} \ No newline at end of file diff --git a/26_Chomp/csharp/PlayerNumber.cs b/26_Chomp/csharp/PlayerNumber.cs new file mode 100644 index 00000000..16138a9b --- /dev/null +++ b/26_Chomp/csharp/PlayerNumber.cs @@ -0,0 +1,32 @@ +namespace Chomp; + +internal class PlayerNumber +{ + private readonly float _playerCount; + private int _counter; + private float _number; + + // The original code does not constrain playerCount to be an integer + public PlayerNumber(float playerCount) + { + _playerCount = playerCount; + _number = 0; + Increment(); + } + + public static PlayerNumber operator ++(PlayerNumber number) => number.Increment(); + + private PlayerNumber Increment() + { + if (_playerCount == 0) { throw new DivideByZeroException(); } + + // The increment logic here is the same as the original program, and exhibits + // interesting behaviour when _playerCount is not an integer. + _counter++; + _number = _counter - (float)Math.Floor(_counter / _playerCount) * _playerCount; + if (_number == 0) { _number = _playerCount; } + return this; + } + + public override string ToString() => (_number >= 0 ? " " : "") + _number.ToString(); +} \ No newline at end of file diff --git a/26_Chomp/csharp/Program.cs b/26_Chomp/csharp/Program.cs new file mode 100644 index 00000000..f541259c --- /dev/null +++ b/26_Chomp/csharp/Program.cs @@ -0,0 +1,5 @@ +global using Games.Common.IO; +global using Chomp.Resources; +using Chomp; + +new Game(new ConsoleIO()).Play(); \ No newline at end of file diff --git a/26_Chomp/csharp/Resources/Coordinates.txt b/26_Chomp/csharp/Resources/Coordinates.txt new file mode 100644 index 00000000..e78dd644 --- /dev/null +++ b/26_Chomp/csharp/Resources/Coordinates.txt @@ -0,0 +1 @@ +Coordinates of Chomp (row, column) \ No newline at end of file diff --git a/26_Chomp/csharp/Resources/HereWeGo.txt b/26_Chomp/csharp/Resources/HereWeGo.txt new file mode 100644 index 00000000..00975d31 --- /dev/null +++ b/26_Chomp/csharp/Resources/HereWeGo.txt @@ -0,0 +1,2 @@ +Here we go... + diff --git a/26_Chomp/csharp/Resources/HowManyColumns.txt b/26_Chomp/csharp/Resources/HowManyColumns.txt new file mode 100644 index 00000000..e323f959 --- /dev/null +++ b/26_Chomp/csharp/Resources/HowManyColumns.txt @@ -0,0 +1 @@ +How many columns \ No newline at end of file diff --git a/26_Chomp/csharp/Resources/HowManyPlayers.txt b/26_Chomp/csharp/Resources/HowManyPlayers.txt new file mode 100644 index 00000000..98ff0ad7 --- /dev/null +++ b/26_Chomp/csharp/Resources/HowManyPlayers.txt @@ -0,0 +1 @@ +How many players \ No newline at end of file diff --git a/26_Chomp/csharp/Resources/HowManyRows.txt b/26_Chomp/csharp/Resources/HowManyRows.txt new file mode 100644 index 00000000..1ad9464e --- /dev/null +++ b/26_Chomp/csharp/Resources/HowManyRows.txt @@ -0,0 +1 @@ +How many rows \ No newline at end of file diff --git a/26_Chomp/csharp/Resources/Introduction.txt b/26_Chomp/csharp/Resources/Introduction.txt new file mode 100644 index 00000000..7c95e7a6 --- /dev/null +++ b/26_Chomp/csharp/Resources/Introduction.txt @@ -0,0 +1,6 @@ + Chomp + Creative Computing Morristown, New Jersey + + + +This is the game of Chomp (Scientific American, Jan 1973) diff --git a/26_Chomp/csharp/Resources/NoFair.txt b/26_Chomp/csharp/Resources/NoFair.txt new file mode 100644 index 00000000..9fe310eb --- /dev/null +++ b/26_Chomp/csharp/Resources/NoFair.txt @@ -0,0 +1 @@ +No fair. You're trying to chomp on empty space! diff --git a/26_Chomp/csharp/Resources/Player.txt b/26_Chomp/csharp/Resources/Player.txt new file mode 100644 index 00000000..f743df3a --- /dev/null +++ b/26_Chomp/csharp/Resources/Player.txt @@ -0,0 +1 @@ +Player{0} \ No newline at end of file diff --git a/26_Chomp/csharp/Resources/Resource.cs b/26_Chomp/csharp/Resources/Resource.cs new file mode 100644 index 00000000..d080b1fb --- /dev/null +++ b/26_Chomp/csharp/Resources/Resource.cs @@ -0,0 +1,48 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Chomp.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream HereWeGo => GetStream(); + public static Stream Introduction => GetStream(); + public static Stream Rules => GetStream(); + public static Stream NoFair => GetStream(); + } + + internal static class Formats + { + public static string Player => GetString(); + public static string YouLose => GetString(); + } + + internal static class Prompts + { + public static string Coordinates => 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/26_Chomp/csharp/Resources/Rules.txt b/26_Chomp/csharp/Resources/Rules.txt new file mode 100644 index 00000000..2fd9177f --- /dev/null +++ b/26_Chomp/csharp/Resources/Rules.txt @@ -0,0 +1,22 @@ +Chomp is for 1 or more players (humans only). + +Here's how a board looks (this one is 5 by 7): + + 1 2 3 4 5 6 7 8 9 + 1 P * * * * * * + 2 * * * * * * * + 3 * * * * * * * + 4 * * * * * * * + 5 * * * * * * * + + +The board is a big cookie - R rows high and C columns +wide. You input R and C at the start. In the upper left +corner of the cookie is a poison square (P). The one who +chomps the poison square loses. To take a chomp, type the +row and column of one of the squares on the cookie. +All of the squares below and to the right of that square +(including that square, too) disappear -- Chomp!! +No fair chomping on squares that have already been chomped, +or that are outside the original dimensions of the cookie. + diff --git a/26_Chomp/csharp/Resources/TooManyColumns.txt b/26_Chomp/csharp/Resources/TooManyColumns.txt new file mode 100644 index 00000000..1e8766a7 --- /dev/null +++ b/26_Chomp/csharp/Resources/TooManyColumns.txt @@ -0,0 +1 @@ +Too many rows (9 is maximum). Now, \ No newline at end of file diff --git a/26_Chomp/csharp/Resources/TooManyRows.txt b/26_Chomp/csharp/Resources/TooManyRows.txt new file mode 100644 index 00000000..1e8766a7 --- /dev/null +++ b/26_Chomp/csharp/Resources/TooManyRows.txt @@ -0,0 +1 @@ +Too many rows (9 is maximum). Now, \ No newline at end of file diff --git a/26_Chomp/csharp/Resources/YouLose.txt b/26_Chomp/csharp/Resources/YouLose.txt new file mode 100644 index 00000000..1015e271 --- /dev/null +++ b/26_Chomp/csharp/Resources/YouLose.txt @@ -0,0 +1 @@ +You lose, player{0} diff --git a/84_Super_Star_Trek/README.md b/84_Super_Star_Trek/README.md index 1edeb869..07da653f 100644 --- a/84_Super_Star_Trek/README.md +++ b/84_Super_Star_Trek/README.md @@ -72,7 +72,7 @@ The relation between the Historical and Standard nomenclatures is shown in the s - If you don’t zap a Klingon hard enough (relative to his shield strength) you won’t damage him at all. Your sensors will tell the story. - Damage control will let you know when out-of-commission devices have been completely repaired. -9. Your engines will automatically shit down if you should attempt to leave the galaxy, or if you should try to maneuver through a star, or Starbase, or—heaven help you—a Klingon warship. +9. Your engines will automatically shut down if you should attempt to leave the galaxy, or if you should try to maneuver through a star, or Starbase, or—heaven help you—a Klingon warship. 10. In a pinch, or if you should miscalculate slightly, some shield control energy will be automatically diverted to warp engine control (if your shield are operational!).