From a5ca0cb17cdd28c5950d7b5592d869ff16d7fdb8 Mon Sep 17 00:00:00 2001 From: ben_singer Date: Fri, 22 Nov 2024 11:30:14 +0000 Subject: [PATCH 1/5] Refactored descriptions, added introductions to rooms --- NetAF.Tests/Commands/Scene/Move_Tests.cs | 43 +++++++++++++++++-- NetAF.Tests/Persistence/RestorePoint_Tests.cs | 2 +- .../Assets/Characters/NonPlayableCharacter.cs | 4 +- NetAF/Assets/Characters/PlayableCharacter.cs | 4 +- NetAF/Assets/ConditionalDescription.cs | 30 ++++--------- NetAF/Assets/Description.cs | 19 +++----- NetAF/Assets/ExaminableObject.cs | 2 +- NetAF/Assets/IDescription.cs | 14 ++++++ NetAF/Assets/IExaminable.cs | 2 +- NetAF/Assets/Item.cs | 2 +- NetAF/Assets/Locations/Exit.cs | 4 +- NetAF/Assets/Locations/Overworld.cs | 2 +- NetAF/Assets/Locations/Region.cs | 2 +- NetAF/Assets/Locations/Room.cs | 40 ++++++++++++++++- NetAF/Commands/Scene/Move.cs | 16 ++++++- 15 files changed, 130 insertions(+), 56 deletions(-) create mode 100644 NetAF/Assets/IDescription.cs diff --git a/NetAF.Tests/Commands/Scene/Move_Tests.cs b/NetAF.Tests/Commands/Scene/Move_Tests.cs index 649f77c0..d4591376 100644 --- a/NetAF.Tests/Commands/Scene/Move_Tests.cs +++ b/NetAF.Tests/Commands/Scene/Move_Tests.cs @@ -14,8 +14,8 @@ public class Move_Tests public void GivenCantMove_WhenInvoke_ThenError() { var region = new Region(Identifier.Empty, Description.Empty); - region.AddRoom(new Room(Identifier.Empty, Description.Empty, [new Exit(Direction.North)]), 0, 0, 0); - region.AddRoom(new Room(Identifier.Empty, Description.Empty, [new Exit(Direction.South)]), 0, 1, 0); + region.AddRoom(new Room(new("Origin"), Description.Empty, [new Exit(Direction.North)]), 0, 0, 0); + region.AddRoom(new Room(new("Target"), Description.Empty, [new Exit(Direction.South)]), 0, 1, 0); var overworld = new Overworld(string.Empty, string.Empty); overworld.AddRegion(region); var game = Game.Create(new GameInfo(string.Empty, string.Empty, string.Empty), string.Empty, AssetGenerator.Retained(overworld, null), GameEndConditions.NoEnd, TestGameConfiguration.Default).Invoke(); @@ -30,8 +30,8 @@ public void GivenCantMove_WhenInvoke_ThenError() public void GivenCanMove_WhenInvoke_ThenSilent() { var region = new Region(Identifier.Empty, Description.Empty); - region.AddRoom(new Room(Identifier.Empty, Description.Empty, [new Exit(Direction.North)]), 0, 0, 0); - region.AddRoom(new Room(Identifier.Empty, Description.Empty, [new Exit(Direction.South)]), 0, 1, 0); + region.AddRoom(new Room(new("Origin"), Description.Empty, [new Exit(Direction.North)]), 0, 0, 0); + region.AddRoom(new Room(new("Target"), Description.Empty, [new Exit(Direction.South)]), 0, 1, 0); var overworld = new Overworld(string.Empty, string.Empty); overworld.AddRegion(region); var game = Game.Create(new GameInfo(string.Empty, string.Empty, string.Empty), string.Empty, AssetGenerator.Retained(overworld, null), GameEndConditions.NoEnd, TestGameConfiguration.Default).Invoke(); @@ -41,5 +41,40 @@ public void GivenCanMove_WhenInvoke_ThenSilent() Assert.AreEqual(ReactionResult.Silent, result.Result); } + + [TestMethod] + public void GivenCanMoveToPreviouslyUnvisitedRoomWithIntroduction_WhenInvoke_ThenInform() + { + var region = new Region(Identifier.Empty, Description.Empty); + region.AddRoom(new Room(new("Origin"), Description.Empty, [new Exit(Direction.North)]), 0, 0, 0); + region.AddRoom(new Room(new("Target"), Description.Empty, new("ABC"), [new Exit(Direction.South)]), 0, 1, 0); + var overworld = new Overworld(string.Empty, string.Empty); + overworld.AddRegion(region); + var game = Game.Create(new GameInfo(string.Empty, string.Empty, string.Empty), string.Empty, AssetGenerator.Retained(overworld, null), GameEndConditions.NoEnd, TestGameConfiguration.Default).Invoke(); + var command = new Move(Direction.North); + + var result = command.Invoke(game); + + Assert.AreEqual(ReactionResult.Inform, result.Result); + } + + [TestMethod] + public void GivenCanMoveToPreviouslyVisitedRoomWithIntroduction_WhenInvoke_ThenSilent() + { + var region = new Region(Identifier.Empty, Description.Empty); + region.AddRoom(new Room(new("Origin"), Description.Empty, [new Exit(Direction.North)]), 0, 0, 0); + region.AddRoom(new Room(new("Target"), Description.Empty, new("ABC"), [new Exit(Direction.South)]), 0, 1, 0); + var overworld = new Overworld(string.Empty, string.Empty); + overworld.AddRegion(region); + var game = Game.Create(new GameInfo(string.Empty, string.Empty, string.Empty), string.Empty, AssetGenerator.Retained(overworld, null), GameEndConditions.NoEnd, TestGameConfiguration.Default).Invoke(); + new Move(Direction.North).Invoke(game); + new Move(Direction.South).Invoke(game); + + var command = new Move(Direction.North); + + var result = command.Invoke(game); + + Assert.AreEqual(ReactionResult.Silent, result.Result); + } } } diff --git a/NetAF.Tests/Persistence/RestorePoint_Tests.cs b/NetAF.Tests/Persistence/RestorePoint_Tests.cs index 00558bf8..1587c234 100644 --- a/NetAF.Tests/Persistence/RestorePoint_Tests.cs +++ b/NetAF.Tests/Persistence/RestorePoint_Tests.cs @@ -65,7 +65,7 @@ public void GivenSimpleGameWithCustomCommand_WhenFromJsonFromNewlyCreatedJson_Th { var command = new CustomCommand(new CommandHelp("TEST COMMAND", string.Empty), true, true, null); var regionMaker = new RegionMaker(string.Empty, string.Empty); - var room = new Room(string.Empty, string.Empty, null, commands: [command]); + var room = new Room(string.Empty, string.Empty, exits: null, commands: [command]); regionMaker[0, 0, 0] = room; var overworldMaker = new OverworldMaker(string.Empty, string.Empty, regionMaker); var game = Game.Create(new GameInfo(string.Empty, string.Empty, string.Empty), string.Empty, AssetGenerator.Retained(overworldMaker.Make(), new PlayableCharacter(string.Empty, string.Empty)), GameEndConditions.NoEnd, TestGameConfiguration.Default).Invoke(); diff --git a/NetAF/Assets/Characters/NonPlayableCharacter.cs b/NetAF/Assets/Characters/NonPlayableCharacter.cs index fde88785..7ff32657 100644 --- a/NetAF/Assets/Characters/NonPlayableCharacter.cs +++ b/NetAF/Assets/Characters/NonPlayableCharacter.cs @@ -34,7 +34,7 @@ public sealed class NonPlayableCharacter : Character, IConverser, IRestoreFromOb /// This objects commands. /// The interaction. /// The examination. - public NonPlayableCharacter(Identifier identifier, Description description, Conversation conversation = null, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) + public NonPlayableCharacter(Identifier identifier, IDescription description, Conversation conversation = null, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) { Identifier = identifier; Description = description; @@ -56,7 +56,7 @@ public NonPlayableCharacter(Identifier identifier, Description description, Conv /// This objects commands. /// The interaction. /// The examination. - public NonPlayableCharacter(Identifier identifier, Description description, bool isAlive, Conversation conversation = null, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) : this(identifier, description, conversation, commands, interaction, examination) + public NonPlayableCharacter(Identifier identifier, IDescription description, bool isAlive, Conversation conversation = null, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) : this(identifier, description, conversation, commands, interaction, examination) { IsAlive = isAlive; Interaction = interaction; diff --git a/NetAF/Assets/Characters/PlayableCharacter.cs b/NetAF/Assets/Characters/PlayableCharacter.cs index 1e0b441d..edeb8e0b 100644 --- a/NetAF/Assets/Characters/PlayableCharacter.cs +++ b/NetAF/Assets/Characters/PlayableCharacter.cs @@ -40,7 +40,7 @@ public sealed class PlayableCharacter : Character /// This objects commands. /// The interaction. /// The examination. - public PlayableCharacter(Identifier identifier, Description description, Item[] items = null, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) : this(identifier, description, true, items, commands, interaction, examination) + public PlayableCharacter(Identifier identifier, IDescription description, Item[] items = null, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) : this(identifier, description, true, items, commands, interaction, examination) { } @@ -68,7 +68,7 @@ public PlayableCharacter(Identifier identifier, Description description, Item[] /// This objects commands. /// The interaction. /// The examination. - public PlayableCharacter(Identifier identifier, Description description, bool canConverse, Item[] items = null, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) + public PlayableCharacter(Identifier identifier, IDescription description, bool canConverse, Item[] items = null, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) { Identifier = identifier; Description = description; diff --git a/NetAF/Assets/ConditionalDescription.cs b/NetAF/Assets/ConditionalDescription.cs index 35ff5272..8e5913f1 100644 --- a/NetAF/Assets/ConditionalDescription.cs +++ b/NetAF/Assets/ConditionalDescription.cs @@ -3,37 +3,23 @@ /// /// Represents a conditional description of an object. /// - /// The true description. - /// The false description. + /// The description of this object when the condition returns true. + /// The description of this object when the condition returns false. /// The condition. - public sealed class ConditionalDescription(string trueDescription, string falseDescription, Condition condition) : Description(trueDescription) + public sealed class ConditionalDescription(string trueDescription, string falseDescription, Condition condition) : IDescription { - #region Properties - - /// - /// Get or set the description for when this condition is false - /// - private readonly string falseDescription = falseDescription; - - /// - /// Get or set the condition - /// - public Condition Condition { get; set; } = condition; - - #endregion - - #region Overrides of Description + #region Implementation of IDescription /// /// Get the description. /// /// The description. - public override string GetDescription() + public string GetDescription() { - if (Condition != null) - return Condition.Invoke() ? DefaultDescription : falseDescription; + if (condition != null) + return condition.Invoke() ? trueDescription : falseDescription; - return DefaultDescription; + return trueDescription; } #endregion diff --git a/NetAF/Assets/Description.cs b/NetAF/Assets/Description.cs index 593f9361..2d76c4f6 100644 --- a/NetAF/Assets/Description.cs +++ b/NetAF/Assets/Description.cs @@ -3,8 +3,8 @@ /// /// Represents a description of an object. /// - /// The description - public class Description(string description) + /// The description of this object. + public sealed class Description(string description) : IDescription { #region StaticProperties @@ -15,24 +15,15 @@ public class Description(string description) #endregion - #region Properties - - /// - /// Get or set the description. - /// - protected string DefaultDescription { get; set; } = description; - - #endregion - - #region Methods + #region Implementation of IDescription /// /// Get the description. /// /// The description. - public virtual string GetDescription() + public string GetDescription() { - return DefaultDescription; + return description; } #endregion diff --git a/NetAF/Assets/ExaminableObject.cs b/NetAF/Assets/ExaminableObject.cs index c21aa3e5..5e8cd3e4 100644 --- a/NetAF/Assets/ExaminableObject.cs +++ b/NetAF/Assets/ExaminableObject.cs @@ -70,7 +70,7 @@ public class ExaminableObject : IExaminable /// /// Get a description of this object. /// - public Description Description { get; protected set; } + public IDescription Description { get; protected set; } /// /// Get this objects commands. diff --git a/NetAF/Assets/IDescription.cs b/NetAF/Assets/IDescription.cs new file mode 100644 index 00000000..25e348ce --- /dev/null +++ b/NetAF/Assets/IDescription.cs @@ -0,0 +1,14 @@ +namespace NetAF.Assets +{ + /// + /// Provides a description of an object. + /// + public interface IDescription + { + /// + /// Get the description. + /// + /// The description. + string GetDescription(); + } +} diff --git a/NetAF/Assets/IExaminable.cs b/NetAF/Assets/IExaminable.cs index 93929674..a3ab6713 100644 --- a/NetAF/Assets/IExaminable.cs +++ b/NetAF/Assets/IExaminable.cs @@ -17,7 +17,7 @@ public interface IExaminable : IPlayerVisible, IRestoreFromObjectSerialization /// Get a description of this object. /// - Description Description { get; } + IDescription Description { get; } /// /// Get this objects commands. /// diff --git a/NetAF/Assets/Item.cs b/NetAF/Assets/Item.cs index 99d41934..b0c3d042 100644 --- a/NetAF/Assets/Item.cs +++ b/NetAF/Assets/Item.cs @@ -47,7 +47,7 @@ public sealed class Item : ExaminableObject, IInteractWithItem, IRestoreFromObje /// This objects commands. /// The interaction. /// The examination. - public Item(Identifier identifier, Description description, bool isTakeable = false, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) + public Item(Identifier identifier, IDescription description, bool isTakeable = false, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) { Identifier = identifier; Description = description; diff --git a/NetAF/Assets/Locations/Exit.cs b/NetAF/Assets/Locations/Exit.cs index f47c1964..5476911e 100644 --- a/NetAF/Assets/Locations/Exit.cs +++ b/NetAF/Assets/Locations/Exit.cs @@ -40,7 +40,7 @@ public sealed class Exit : ExaminableObject, IInteractWithItem, IRestoreFromObje /// This objects commands. /// The interaction. /// The examination. - public Exit(Direction direction, bool isLocked = false, Identifier identifier = null, Description description = null, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) + public Exit(Direction direction, bool isLocked = false, Identifier identifier = null, IDescription description = null, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) { Identifier = identifier ?? new(direction.ToString()); Direction = direction; @@ -61,7 +61,7 @@ public Exit(Direction direction, bool isLocked = false, Identifier identifier = /// Generate a description for this exit. /// /// The completed Description. - private Description GenerateDescription() + private IDescription GenerateDescription() { return new ConditionalDescription($"The exit {Direction.ToString().ToLower()} is locked.", $"The exit {Direction.ToString().ToLower()} is unlocked.", () => IsLocked); } diff --git a/NetAF/Assets/Locations/Overworld.cs b/NetAF/Assets/Locations/Overworld.cs index 4173ed48..f8b05d01 100644 --- a/NetAF/Assets/Locations/Overworld.cs +++ b/NetAF/Assets/Locations/Overworld.cs @@ -56,7 +56,7 @@ public Region CurrentRegion /// A description of this overworld. /// This objects commands. /// The examination. - public Overworld(Identifier identifier, Description description, CustomCommand[] commands = null, ExaminationCallback examination = null) + public Overworld(Identifier identifier, IDescription description, CustomCommand[] commands = null, ExaminationCallback examination = null) { Identifier = identifier; Description = description; diff --git a/NetAF/Assets/Locations/Region.cs b/NetAF/Assets/Locations/Region.cs index 7889f8ec..a12d5e7e 100644 --- a/NetAF/Assets/Locations/Region.cs +++ b/NetAF/Assets/Locations/Region.cs @@ -86,7 +86,7 @@ public Room CurrentRoom /// The description of this Region. /// This objects commands. /// The examination. - public Region(Identifier identifier, Description description, CustomCommand[] commands = null, ExaminationCallback examination = null) + public Region(Identifier identifier, IDescription description, CustomCommand[] commands = null, ExaminationCallback examination = null) { Identifier = identifier; Description = description; diff --git a/NetAF/Assets/Locations/Room.cs b/NetAF/Assets/Locations/Room.cs index 3a0da8ed..e55387a0 100644 --- a/NetAF/Assets/Locations/Room.cs +++ b/NetAF/Assets/Locations/Room.cs @@ -54,6 +54,11 @@ public sealed class Room : ExaminableObject, IInteractWithItem, IItemContainer, /// public Direction? EnteredFrom { get; private set; } + /// + /// Get an introduction for this Room. + /// + public IDescription Introduction { get; private set; } + #endregion #region Constructors @@ -68,7 +73,36 @@ public sealed class Room : ExaminableObject, IInteractWithItem, IItemContainer, /// This objects commands. /// The interaction. /// The examination. - public Room(string identifier, string description, Exit[] exits = null, Item[] items = null, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) : this(new Identifier(identifier), new Description(description), exits, items, commands, interaction, examination) + public Room(string identifier, string description, Exit[] exits = null, Item[] items = null, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) : this(new Identifier(identifier), new Description(description), Assets.Description.Empty, exits, items, commands, interaction, examination) + { + } + + /// + /// Initializes a new instance of the Room class. + /// + /// This rooms identifier. + /// This rooms description. + /// The exits from this room. + /// The items in this room. + /// This objects commands. + /// The interaction. + /// The examination. + public Room(Identifier identifier, IDescription description, Exit[] exits = null, Item[] items = null, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) : this(identifier, description, Assets.Description.Empty, exits, items, commands, interaction, examination) + { + } + + /// + /// Initializes a new instance of the Room class. + /// + /// This rooms identifier. + /// This rooms description. + /// An introduction to this room. + /// The exits from this room. + /// The items in this room. + /// This objects commands. + /// The interaction. + /// The examination. + public Room(string identifier, string description, string introduction, Exit[] exits = null, Item[] items = null, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) : this(new Identifier(identifier), new Description(description), new Description(introduction), exits, items, commands, interaction, examination) { } @@ -77,15 +111,17 @@ public sealed class Room : ExaminableObject, IInteractWithItem, IItemContainer, /// /// This rooms identifier. /// This rooms description. + /// An introduction to this room. /// The exits from this room. /// The items in this room. /// This objects commands. /// The interaction. /// The examination. - public Room(Identifier identifier, Description description, Exit[] exits = null, Item[] items = null, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) + public Room(Identifier identifier, IDescription description, Description introduction, Exit[] exits = null, Item[] items = null, CustomCommand[] commands = null, InteractionCallback interaction = null, ExaminationCallback examination = null) { Identifier = identifier; Description = description; + Introduction = introduction; Exits = exits ?? []; Items = items ?? []; Commands = commands ?? []; diff --git a/NetAF/Commands/Scene/Move.cs b/NetAF/Commands/Scene/Move.cs index 4b3d0d6c..07b5d3b3 100644 --- a/NetAF/Commands/Scene/Move.cs +++ b/NetAF/Commands/Scene/Move.cs @@ -1,4 +1,5 @@ -using NetAF.Assets.Locations; +using NetAF.Assets; +using NetAF.Assets.Locations; namespace NetAF.Commands.Scene { @@ -54,8 +55,19 @@ public Reaction Invoke(Logic.Game game) if (game == null) return new(ReactionResult.Error, "No game specified."); - if (game.Overworld.CurrentRegion.Move(direction)) + var region = game.Overworld.CurrentRegion; + var targetRoom = region.GetAdjoiningRoom(direction); + var movingToPreviouslyUnvisitedRoom = targetRoom != null && !targetRoom.HasBeenVisited; + + if (region.Move(direction)) + { + var introduction = targetRoom?.Introduction?.GetDescription() ?? string.Empty; + + if (movingToPreviouslyUnvisitedRoom && !string.IsNullOrEmpty(introduction)) + return new(ReactionResult.Inform, introduction); + return new(ReactionResult.Silent, $"Moved {direction}."); + } return new(ReactionResult.Error, $"Could not move {direction}."); } From 521993341163efadc9ca534ed30e58d31d623cff Mon Sep 17 00:00:00 2001 From: ben_singer Date: Fri, 22 Nov 2024 11:31:28 +0000 Subject: [PATCH 2/5] Made examine description less jarring --- NetAF/Commands/Scene/Examine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NetAF/Commands/Scene/Examine.cs b/NetAF/Commands/Scene/Examine.cs index 4b108118..b5cad198 100644 --- a/NetAF/Commands/Scene/Examine.cs +++ b/NetAF/Commands/Scene/Examine.cs @@ -13,7 +13,7 @@ public sealed class Examine(IExaminable examinable) : ICommand /// /// Get the command help. /// - public static CommandHelp CommandHelp { get; } = new("Examine", "Examine anything in the game", "X"); + public static CommandHelp CommandHelp { get; } = new("Examine", "Examine something", "X"); #endregion From f74c4bf62247b30cc2f28eb79cc0f2377016e11c Mon Sep 17 00:00:00 2001 From: ben_singer Date: Fri, 22 Nov 2024 12:26:25 +0000 Subject: [PATCH 3/5] Made z locations pannable when not directly above or below --- NetAF.Tests/Assets/Locations/Matrix_Tests.cs | 34 +++++++ .../Logic/Modes/RegionMapMode_Tests.cs | 69 +++++++++++++++ NetAF/Assets/Locations/Matrix.cs | 50 +++++++++++ NetAF/Assets/Locations/Region.cs | 1 + NetAF/Logic/Modes/RegionMapMode.cs | 88 ++++++++++++++++++- 5 files changed, 239 insertions(+), 3 deletions(-) diff --git a/NetAF.Tests/Assets/Locations/Matrix_Tests.cs b/NetAF.Tests/Assets/Locations/Matrix_Tests.cs index 9e4a4631..4177047f 100644 --- a/NetAF.Tests/Assets/Locations/Matrix_Tests.cs +++ b/NetAF.Tests/Assets/Locations/Matrix_Tests.cs @@ -105,5 +105,39 @@ public void Given4Rooms_WhenToRooms_Then4Rooms() Assert.AreEqual(4, result.Length); } + + [TestMethod] + public void Given2RoomsOnZ2_WhenFindAllRoomsOnZ_Then2Rooms() + { + List roomPositions = + [ + new(new(string.Empty, string.Empty), new Point3D(0, 0, 0)), + new(new(string.Empty, string.Empty), new Point3D(0, 1, 0)), + new(new(string.Empty, string.Empty), new Point3D(0, 1, 1)), + new(new(string.Empty, string.Empty), new Point3D(0, 1, 2)) + ]; + var matrix = new Matrix([.. roomPositions]); + + var result = matrix.FindAllRoomsOnZ(0); + + Assert.AreEqual(2, result.Length); + } + + [TestMethod] + public void Given1RoomOnZ1_WhenFindAllRoomsOnZ_Then1Room() + { + List roomPositions = + [ + new(new(string.Empty, string.Empty), new Point3D(0, 0, 0)), + new(new(string.Empty, string.Empty), new Point3D(0, 1, 0)), + new(new(string.Empty, string.Empty), new Point3D(0, 1, 1)), + new(new(string.Empty, string.Empty), new Point3D(0, 1, 2)) + ]; + var matrix = new Matrix([.. roomPositions]); + + var result = matrix.FindAllRoomsOnZ(1); + + Assert.AreEqual(1, result.Length); + } } } diff --git a/NetAF.Tests/Logic/Modes/RegionMapMode_Tests.cs b/NetAF.Tests/Logic/Modes/RegionMapMode_Tests.cs index 6ee86fd3..d10b2438 100644 --- a/NetAF.Tests/Logic/Modes/RegionMapMode_Tests.cs +++ b/NetAF.Tests/Logic/Modes/RegionMapMode_Tests.cs @@ -80,5 +80,74 @@ public void GivenPositionInBoundsButNotVisitedButRegionIsVisibleWithoutDiscovery Assert.IsTrue(result); } + + [TestMethod] + public void GivenAnOffsetZ_WhenPanToPosition_ThenReturnTrue() + { + RegionMaker regionMaker = new(string.Empty, string.Empty); + Room room = new(string.Empty, string.Empty); + Room room2 = new(string.Empty, string.Empty); + regionMaker[0, 0, 0] = room; + regionMaker[1, 0, 1] = room2; + var region = regionMaker.Make(); + region.JumpToRoom(new(1, 0, 1)); + region.JumpToRoom(new(0, 0, 0)); + + var result = RegionMapMode.CanPanToPosition(region, new Point3D(0, 0, 1)); + + Assert.IsTrue(result); + } + + [TestMethod] + public void GivenAnOffsetZPositionButNotVisited_WhenPanToPosition_ThenReturnFalse() + { + RegionMaker regionMaker = new(string.Empty, string.Empty); + Room room = new(string.Empty, string.Empty); + Room room2 = new(string.Empty, string.Empty); + regionMaker[0, 0, 0] = room; + regionMaker[1, 0, 1] = room2; + var region = regionMaker.Make(); + + var result = RegionMapMode.CanPanToPosition(region, new Point3D(0, 0, 1)); + + Assert.IsFalse(result); + } + + [TestMethod] + public void GivenAnOffsetZPositionButNotVisitedButRegionIsVisibleWithoutDiscoveryIsTrue_WhenPanToPosition_ThenReturnTrue() + { + RegionMaker regionMaker = new(string.Empty, string.Empty); + Room room = new(string.Empty, string.Empty); + Room room2 = new(string.Empty, string.Empty); + regionMaker[0, 0, 0] = room; + regionMaker[1, 0, 1] = room2; + var region = regionMaker.Make(); + region.IsVisibleWithoutDiscovery = true; + + var result = RegionMapMode.CanPanToPosition(region, new Point3D(0, 0, 1)); + + Assert.IsTrue(result); + } + + [TestMethod] + public void GivenAnOffsetZPosition_WhenRender_ThenNoException() + { + Assertions.NoExceptionThrown(() => + { + RegionMaker regionMaker = new(string.Empty, string.Empty); + Room room = new(string.Empty, string.Empty); + Room room2 = new(string.Empty, string.Empty); + regionMaker[0, 0, 0] = room; + regionMaker[1, 0, 1] = room2; + OverworldMaker overworldMaker = new(string.Empty, string.Empty, regionMaker); + var game = Game.Create(new(string.Empty, string.Empty, string.Empty), string.Empty, AssetGenerator.Retained(overworldMaker.Make(), new PlayableCharacter(string.Empty, string.Empty)), GameEndConditions.NoEnd, TestGameConfiguration.Default).Invoke(); + var mode = new RegionMapMode(new Point3D(0, 0, 1)); + + game.Overworld.CurrentRegion.JumpToRoom(new(1, 0, 1)); + game.Overworld.CurrentRegion.JumpToRoom(new(0, 0, 0)); + + mode.Render(game); + }); + } } } diff --git a/NetAF/Assets/Locations/Matrix.cs b/NetAF/Assets/Locations/Matrix.cs index 7b988670..12c4cdf2 100644 --- a/NetAF/Assets/Locations/Matrix.cs +++ b/NetAF/Assets/Locations/Matrix.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Security.Cryptography.X509Certificates; namespace NetAF.Assets.Locations { @@ -26,6 +27,13 @@ public sealed class Matrix(RoomPosition[] roomPositions) /// The room. public Room this[int x, int y, int z] => Array.Find(roomPositions, r => r.IsAtPosition(x, y, z))?.Room; + /// + /// Get the position of a room in this matrix. + /// + /// The room. + /// The position of the room, else false. + public Point3D? this[Room room] => Array.Find(roomPositions, r => r.Room == room)?.Position; + /// /// Get the width of the matrix. /// @@ -54,6 +62,48 @@ public Room[] ToRooms() return roomPositions.Select(x => x.Room).ToArray(); } + /// + /// Find all rooms on a specified Z plane. + /// + /// The Z plane. + /// All rooms on the specified Z plane. + public Room[] FindAllRoomsOnZ(int z) + { + return roomPositions.Where(r => r.Position.Z == z).Select(r => r.Room).ToArray(); + } + + /// + /// Find the distance between two rooms. + /// + /// Room a. + /// Room b. + /// The distance between the two rooms. + public double DistanceBetweenRooms(Room a, Room b) + { + var aPos = this[a]; + var bPos = this[b]; + + if (!aPos.HasValue || !bPos.HasValue) + return 0d; + + return DistanceBetweenPoints(aPos.Value, bPos.Value); + } + + #endregion + + #region StaticMethods + + /// + /// Find the distance between two points. + /// + /// Point a. + /// Point b. + /// The distance between the two points. + public static double DistanceBetweenPoints(Point3D a, Point3D b) + { + return Math.Sqrt(Math.Pow(b.X - a.X, 2) + Math.Pow(b.Y - a.Y, 2) + Math.Pow(b.Z - a.Z, 2)); + } + #endregion } } diff --git a/NetAF/Assets/Locations/Region.cs b/NetAF/Assets/Locations/Region.cs index a12d5e7e..fe216bc4 100644 --- a/NetAF/Assets/Locations/Region.cs +++ b/NetAF/Assets/Locations/Region.cs @@ -280,6 +280,7 @@ public bool JumpToRoom(Point3D location) return false; CurrentRoom = roomPosition.Room; + CurrentRoom.MovedInto(null); return true; } diff --git a/NetAF/Logic/Modes/RegionMapMode.cs b/NetAF/Logic/Modes/RegionMapMode.cs index f2d377e2..8bbcf186 100644 --- a/NetAF/Logic/Modes/RegionMapMode.cs +++ b/NetAF/Logic/Modes/RegionMapMode.cs @@ -1,6 +1,9 @@ using NetAF.Assets; using NetAF.Assets.Locations; using NetAF.Interpretation; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices.ComTypes; namespace NetAF.Logic.Modes { @@ -46,11 +49,15 @@ public sealed class RegionMapMode(Point3D focusPosition) : IGameMode /// The game. public void Render(Game game) { + var region = game.Overworld.CurrentRegion; + // if focusing on the player, find their location if (FocusPosition.Equals(Player)) - FocusPosition = game.Overworld.CurrentRegion.GetPositionOfRoom(game.Overworld.CurrentRegion.CurrentRoom).Position; + FocusPosition = region.GetPositionOfRoom(game.Overworld.CurrentRegion.CurrentRoom).Position; + + FocusPosition = GetSnappedLocation(region, FocusPosition); - var frame = game.Configuration.FrameBuilders.RegionMapFrameBuilder.Build(game.Overworld.CurrentRegion, FocusPosition, Interpreter?.GetContextualCommandHelp(game) ?? [], game.Configuration.DisplaySize); + var frame = game.Configuration.FrameBuilders.RegionMapFrameBuilder.Build(region, FocusPosition, Interpreter?.GetContextualCommandHelp(game) ?? [], game.Configuration.DisplaySize); game.Configuration.Adapter.RenderFrame(frame); } @@ -65,10 +72,85 @@ public void Render(Game game) /// The position. /// True if the pan position is valid, else false. public static bool CanPanToPosition(Region region, Point3D position) + { + var currentRoom = region.CurrentRoom; + var positionOfCurrentRoom = region.GetPositionOfRoom(currentRoom); + + if (positionOfCurrentRoom.Position.Z != position.Z) + return CanPanToZ(region, position); + else + return CanPanToFixedLocation(region, position); + } + + /// + /// Determine if a room can be panned to. + /// + /// The room. + /// If the region is visible without discovery.. + /// True if the room can be panned to, else false. + private static bool IsRoomPannable(Room room, bool regionIsVisibleWithoutDiscovery) + { + return room != null && (room.HasBeenVisited || regionIsVisibleWithoutDiscovery); + } + + /// + /// Determine if a pan position is valid on a different Z. + /// + /// The region. + /// The position. + /// True if the pan position is valid, else false. + private static bool CanPanToZ(Region region, Point3D position) + { + if (CanPanToFixedLocation(region, position)) + return true; + + // may still be able to pan if there is a room on the Z plane and the region is visible without discovery OR a room on that Z plane has been visited + var matrix = region.ToMatrix(); + var allRoomsOnSpecifiedZLevel = matrix.FindAllRoomsOnZ(position.Z); + + foreach (var room in allRoomsOnSpecifiedZLevel) + { + if (IsRoomPannable(room, region.IsVisibleWithoutDiscovery)) + return true; + } + + return false; + } + + /// + /// Determine if a pan position is valid. + /// + /// The region. + /// The position. + /// True if the pan position is valid, else false. + private static bool CanPanToFixedLocation(Region region, Point3D position) { var matrix = region.ToMatrix(); var room = matrix[position.X, position.Y, position.Z]; - return room != null && (room.HasBeenVisited || region.IsVisibleWithoutDiscovery); + return IsRoomPannable(room, region.IsVisibleWithoutDiscovery); + } + + /// + /// Get a snapped pan location. This will handle situations where a position may not be available but a nearby point is. + /// + /// The region. + /// The position. + /// The snapped position. + private static Point3D GetSnappedLocation(Region region, Point3D position) + { + if (CanPanToFixedLocation(region, position)) + return position; + + var matrix = region.ToMatrix(); + var allRoomsOnSpecifiedZLevel = matrix.FindAllRoomsOnZ(position.Z); + var allSnabbableRooms = allRoomsOnSpecifiedZLevel.Where(x => IsRoomPannable(x, region.IsVisibleWithoutDiscovery)); + Dictionary distances = []; + + foreach (var room in allSnabbableRooms) + distances.Add(room, Matrix.DistanceBetweenPoints(position, matrix[room].Value)); + + var targetRoom = distances.OrderBy(x => x.Value).First().Key; + return matrix[targetRoom].Value; } #endregion From f9f59789a1448c8ae110578735b119d852208022 Mon Sep 17 00:00:00 2001 From: ben_singer Date: Fri, 22 Nov 2024 13:34:53 +0000 Subject: [PATCH 4/5] Fixed pull request issues --- .../Assets/Regions/Everglades/Rooms/InnerCave.cs | 5 ++--- NetAF.Tests/Assets/ConditionalDescription_Tests.cs | 10 ++++++++++ NetAF.Tests/Assets/Locations/Matrix_Tests.cs | 11 +++++++++++ NetAF/Assets/Locations/Room.cs | 9 --------- NetAF/Logic/Modes/RegionMapMode.cs | 11 +---------- 5 files changed, 24 insertions(+), 22 deletions(-) diff --git a/NetAF.Examples/Assets/Regions/Everglades/Rooms/InnerCave.cs b/NetAF.Examples/Assets/Regions/Everglades/Rooms/InnerCave.cs index 32d02aa5..fe4ccb59 100644 --- a/NetAF.Examples/Assets/Regions/Everglades/Rooms/InnerCave.cs +++ b/NetAF.Examples/Assets/Regions/Everglades/Rooms/InnerCave.cs @@ -25,7 +25,8 @@ public Room Instantiate() { Room room = null; - room = new Room(Name, string.Empty, [new Exit(Direction.West), new Exit(Direction.North, true)], interaction: item => + var description = new ConditionalDescription("With the bats gone there is daylight to the north. To the west is the cave entrance", "As you enter the inner cave the screeching gets louder, and in the gloom you can make out what looks like a million sets of eyes looking back at you. Bats! You can just make out a few rays of light coming from the north, but the bats are blocking your way.", () => !room[Direction.North].IsLocked); + room = new Room(new(Name), description, [new Exit(Direction.West), new Exit(Direction.North, true)], interaction: item => { if (item != null && ConchShell.Name.EqualsExaminable(item)) { @@ -39,8 +40,6 @@ public Room Instantiate() return new(InteractionResult.NoChange, item); }); - room.SpecifyConditionalDescription(new ConditionalDescription("With the bats gone there is daylight to the north. To the west is the cave entrance", "As you enter the inner cave the screeching gets louder, and in the gloom you can make out what looks like a million sets of eyes looking back at you. Bats! You can just make out a few rays of light coming from the north, but the bats are blocking your way.", () => !room[Direction.North].IsLocked)); - return room; } diff --git a/NetAF.Tests/Assets/ConditionalDescription_Tests.cs b/NetAF.Tests/Assets/ConditionalDescription_Tests.cs index efe8f18f..22018a59 100644 --- a/NetAF.Tests/Assets/ConditionalDescription_Tests.cs +++ b/NetAF.Tests/Assets/ConditionalDescription_Tests.cs @@ -6,6 +6,16 @@ namespace NetAF.Tests.Assets [TestClass] public class ConditionalDescription_Tests { + [TestMethod] + public void GivenGetDescription_WhenNull_ThenReturnTrueDescription() + { + var conditional = new ConditionalDescription("A", "B", null); + + var result = conditional.GetDescription(); + + Assert.AreEqual("A", result); + } + [TestMethod] public void GivenGetDescription_WhenTrue_ThenReturnTrueDescription() { diff --git a/NetAF.Tests/Assets/Locations/Matrix_Tests.cs b/NetAF.Tests/Assets/Locations/Matrix_Tests.cs index 4177047f..80e02e9f 100644 --- a/NetAF.Tests/Assets/Locations/Matrix_Tests.cs +++ b/NetAF.Tests/Assets/Locations/Matrix_Tests.cs @@ -139,5 +139,16 @@ public void Given1RoomOnZ1_WhenFindAllRoomsOnZ_Then1Room() Assert.AreEqual(1, result.Length); } + + [TestMethod] + public void Given2Point1UnitApart_WhenDistanceBetweenPoints_Then1() + { + Point3D a = new(0, 0, 0); + Point3D b = new(1, 0, 0); + + var result = Matrix.DistanceBetweenPoints(a, b); + + Assert.AreEqual(1, (int)result); + } } } diff --git a/NetAF/Assets/Locations/Room.cs b/NetAF/Assets/Locations/Room.cs index e55387a0..a94e28f4 100644 --- a/NetAF/Assets/Locations/Room.cs +++ b/NetAF/Assets/Locations/Room.cs @@ -439,15 +439,6 @@ public bool FindCharacter(string characterName, out NonPlayableCharacter charact return false; } - /// - /// Specify a conditional description of this room. - /// - /// The description of this room. - public void SpecifyConditionalDescription(ConditionalDescription description) - { - Description = description; - } - /// /// Handle movement into this GameLocation. /// diff --git a/NetAF/Logic/Modes/RegionMapMode.cs b/NetAF/Logic/Modes/RegionMapMode.cs index 8bbcf186..6d6f9801 100644 --- a/NetAF/Logic/Modes/RegionMapMode.cs +++ b/NetAF/Logic/Modes/RegionMapMode.cs @@ -105,16 +105,7 @@ private static bool CanPanToZ(Region region, Point3D position) return true; // may still be able to pan if there is a room on the Z plane and the region is visible without discovery OR a room on that Z plane has been visited - var matrix = region.ToMatrix(); - var allRoomsOnSpecifiedZLevel = matrix.FindAllRoomsOnZ(position.Z); - - foreach (var room in allRoomsOnSpecifiedZLevel) - { - if (IsRoomPannable(room, region.IsVisibleWithoutDiscovery)) - return true; - } - - return false; + return region.ToMatrix().FindAllRoomsOnZ(position.Z).Where(x => IsRoomPannable(x, region.IsVisibleWithoutDiscovery)).ToArray().Length > 0; } /// From cac7dd3aa3fb86f9b0b8235b52556c4c74fa3ad8 Mon Sep 17 00:00:00 2001 From: ben_singer Date: Fri, 22 Nov 2024 13:39:39 +0000 Subject: [PATCH 5/5] Tidying of some remaining logic --- NetAF.Tests/Assets/Locations/Room_Tests.cs | 2 +- NetAF/Assets/Locations/Region.cs | 4 ++-- NetAF/Assets/Locations/Room.cs | 20 ++++++++++++++------ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/NetAF.Tests/Assets/Locations/Room_Tests.cs b/NetAF.Tests/Assets/Locations/Room_Tests.cs index b57185bf..3fbd6d07 100644 --- a/NetAF.Tests/Assets/Locations/Room_Tests.cs +++ b/NetAF.Tests/Assets/Locations/Room_Tests.cs @@ -19,7 +19,7 @@ public void GivenNotBeenVisited_WhenGetHasBeenVisited_ThenFalse() public void GivenVisited_WhenGetHasBeenVisited_ThenTrue() { var room = new Room(string.Empty, string.Empty); - room.MovedInto(null); + room.MovedInto(); Assert.IsTrue(room.HasBeenVisited); } diff --git a/NetAF/Assets/Locations/Region.cs b/NetAF/Assets/Locations/Region.cs index fe216bc4..27e563af 100644 --- a/NetAF/Assets/Locations/Region.cs +++ b/NetAF/Assets/Locations/Region.cs @@ -211,7 +211,7 @@ public bool Move(Direction direction) public void SetStartRoom(Room room) { CurrentRoom = room; - CurrentRoom.MovedInto(null); + CurrentRoom.MovedInto(); } /// @@ -280,7 +280,7 @@ public bool JumpToRoom(Point3D location) return false; CurrentRoom = roomPosition.Room; - CurrentRoom.MovedInto(null); + CurrentRoom.MovedInto(); return true; } diff --git a/NetAF/Assets/Locations/Room.cs b/NetAF/Assets/Locations/Room.cs index a94e28f4..aecf44df 100644 --- a/NetAF/Assets/Locations/Room.cs +++ b/NetAF/Assets/Locations/Room.cs @@ -50,12 +50,12 @@ public sealed class Room : ExaminableObject, IInteractWithItem, IItemContainer, public Exit this[Direction direction] => Array.Find(Exits, e => e.Direction == direction); /// - /// Get which direction this Room was entered from. + /// Get which direction this room was entered from. /// public Direction? EnteredFrom { get; private set; } /// - /// Get an introduction for this Room. + /// Get an introduction for this room. /// public IDescription Introduction { get; private set; } @@ -440,15 +440,23 @@ public bool FindCharacter(string characterName, out NonPlayableCharacter charact } /// - /// Handle movement into this GameLocation. + /// Handle movement into this room. /// - /// The direction movement into this Room is from. Use null if there is no direction. - public void MovedInto(Direction? fromDirection) + public void MovedInto() { - EnteredFrom = fromDirection; HasBeenVisited = true; } + /// + /// Handle movement into this room. + /// + /// The direction movement into this room. + public void MovedInto(Direction fromDirection) + { + EnteredFrom = fromDirection; + MovedInto(); + } + #endregion #region IInteractWithItem Members