From dbd34722420b73ddce6866fb8f7eff827aea98d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 21:24:59 +0000 Subject: [PATCH 1/5] Initial plan From ed12c99a842299ac7f73cd810224580b89efba00 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 21:32:28 +0000 Subject: [PATCH 2/5] Add ClassCrawler C# .NET 8 teaching scaffold Co-authored-by: Merlissa09 <41809281+Merlissa09@users.noreply.github.com> --- .github/workflows/ci.yml | 30 +++++++++++ ClassCrawler.Tests/ClassCrawler.Tests.csproj | 27 ++++++++++ ClassCrawler.Tests/CombatEngineTests.cs | 12 +++++ ClassCrawler.Tests/CombatStrategyTests.cs | 12 +++++ ClassCrawler.Tests/InventoryTests.cs | 12 +++++ ClassCrawler.Tests/ItemTests.cs | 12 +++++ ClassCrawler.Tests/LootResolverTests.cs | 12 +++++ ClassCrawler.slnx | 4 ++ ClassCrawler/Characters/Character.cs | 41 +++++++++++++++ ClassCrawler/Characters/Dragon.cs | 32 ++++++++++++ ClassCrawler/Characters/Enemy.cs | 19 +++++++ ClassCrawler/Characters/Goblin.cs | 32 ++++++++++++ ClassCrawler/Characters/Orc.cs | 32 ++++++++++++ ClassCrawler/Characters/Player.cs | 51 +++++++++++++++++++ ClassCrawler/ClassCrawler.csproj | 14 +++++ ClassCrawler/Combat/CharacterFactory.cs | 21 ++++++++ ClassCrawler/Combat/CombatEngine.cs | 14 +++++ ClassCrawler/Combat/CombatOutcome.cs | 4 ++ ClassCrawler/Combat/CombatResult.cs | 4 ++ ClassCrawler/Combat/ICombatStrategy.cs | 10 ++++ ClassCrawler/Combat/LootResolver.cs | 15 ++++++ ClassCrawler/Combat/MagicStrategy.cs | 14 +++++ ClassCrawler/Combat/MeleeStrategy.cs | 14 +++++ ClassCrawler/Combat/RangedStrategy.cs | 14 +++++ ClassCrawler/Events/EnemyDefeatedEvent.cs | 9 ++++ ClassCrawler/Events/GameEvent.cs | 18 +++++++ ClassCrawler/Events/GameEventSystem.cs | 26 ++++++++++ ClassCrawler/Events/GameEventType.cs | 17 +++++++ ClassCrawler/Events/IGameEventListener.cs | 8 +++ ClassCrawler/Events/ItemPickedUpEvent.cs | 9 ++++ ClassCrawler/Events/PlayerDeathEvent.cs | 9 ++++ ClassCrawler/Events/PlayerLevelUpEvent.cs | 9 ++++ ClassCrawler/Items/Armor.cs | 17 +++++++ ClassCrawler/Items/Item.cs | 19 +++++++ ClassCrawler/Items/Potion.cs | 17 +++++++ ClassCrawler/Items/Weapon.cs | 17 +++++++ ClassCrawler/Persistence/GameState.cs | 9 ++++ ClassCrawler/Persistence/IGameRepository.cs | 17 +++++++ ClassCrawler/Persistence/LeaderboardEntry.cs | 4 ++ .../Persistence/SqliteGameRepository.cs | 33 ++++++++++++ ClassCrawler/Program.cs | 3 ++ ClassCrawler/UI/GameUI.cs | 42 +++++++++++++++ ClassCrawler/World/Dungeon.cs | 25 +++++++++ ClassCrawler/World/Room.cs | 26 ++++++++++ README.md | 51 ++++++++++++++++++- 45 files changed, 836 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml create mode 100644 ClassCrawler.Tests/ClassCrawler.Tests.csproj create mode 100644 ClassCrawler.Tests/CombatEngineTests.cs create mode 100644 ClassCrawler.Tests/CombatStrategyTests.cs create mode 100644 ClassCrawler.Tests/InventoryTests.cs create mode 100644 ClassCrawler.Tests/ItemTests.cs create mode 100644 ClassCrawler.Tests/LootResolverTests.cs create mode 100644 ClassCrawler.slnx create mode 100644 ClassCrawler/Characters/Character.cs create mode 100644 ClassCrawler/Characters/Dragon.cs create mode 100644 ClassCrawler/Characters/Enemy.cs create mode 100644 ClassCrawler/Characters/Goblin.cs create mode 100644 ClassCrawler/Characters/Orc.cs create mode 100644 ClassCrawler/Characters/Player.cs create mode 100644 ClassCrawler/ClassCrawler.csproj create mode 100644 ClassCrawler/Combat/CharacterFactory.cs create mode 100644 ClassCrawler/Combat/CombatEngine.cs create mode 100644 ClassCrawler/Combat/CombatOutcome.cs create mode 100644 ClassCrawler/Combat/CombatResult.cs create mode 100644 ClassCrawler/Combat/ICombatStrategy.cs create mode 100644 ClassCrawler/Combat/LootResolver.cs create mode 100644 ClassCrawler/Combat/MagicStrategy.cs create mode 100644 ClassCrawler/Combat/MeleeStrategy.cs create mode 100644 ClassCrawler/Combat/RangedStrategy.cs create mode 100644 ClassCrawler/Events/EnemyDefeatedEvent.cs create mode 100644 ClassCrawler/Events/GameEvent.cs create mode 100644 ClassCrawler/Events/GameEventSystem.cs create mode 100644 ClassCrawler/Events/GameEventType.cs create mode 100644 ClassCrawler/Events/IGameEventListener.cs create mode 100644 ClassCrawler/Events/ItemPickedUpEvent.cs create mode 100644 ClassCrawler/Events/PlayerDeathEvent.cs create mode 100644 ClassCrawler/Events/PlayerLevelUpEvent.cs create mode 100644 ClassCrawler/Items/Armor.cs create mode 100644 ClassCrawler/Items/Item.cs create mode 100644 ClassCrawler/Items/Potion.cs create mode 100644 ClassCrawler/Items/Weapon.cs create mode 100644 ClassCrawler/Persistence/GameState.cs create mode 100644 ClassCrawler/Persistence/IGameRepository.cs create mode 100644 ClassCrawler/Persistence/LeaderboardEntry.cs create mode 100644 ClassCrawler/Persistence/SqliteGameRepository.cs create mode 100644 ClassCrawler/Program.cs create mode 100644 ClassCrawler/UI/GameUI.cs create mode 100644 ClassCrawler/World/Dungeon.cs create mode 100644 ClassCrawler/World/Room.cs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5dfb42b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + +jobs: + build-and-test: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --no-restore + + - name: Test + run: dotnet test --no-build diff --git a/ClassCrawler.Tests/ClassCrawler.Tests.csproj b/ClassCrawler.Tests/ClassCrawler.Tests.csproj new file mode 100644 index 0000000..e0c67fe --- /dev/null +++ b/ClassCrawler.Tests/ClassCrawler.Tests.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + diff --git a/ClassCrawler.Tests/CombatEngineTests.cs b/ClassCrawler.Tests/CombatEngineTests.cs new file mode 100644 index 0000000..d8a52d1 --- /dev/null +++ b/ClassCrawler.Tests/CombatEngineTests.cs @@ -0,0 +1,12 @@ +namespace ClassCrawler.Tests; + +/// Placeholder tests for CombatEngine. Students will complete these. +public class CombatEngineTests +{ + [Fact(Skip = "Not yet implemented")] + public void RunCombat_ReturnsOutcome() + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler.Tests/CombatStrategyTests.cs b/ClassCrawler.Tests/CombatStrategyTests.cs new file mode 100644 index 0000000..729c592 --- /dev/null +++ b/ClassCrawler.Tests/CombatStrategyTests.cs @@ -0,0 +1,12 @@ +namespace ClassCrawler.Tests; + +/// Placeholder tests for combat strategies. Students will complete these. +public class CombatStrategyTests +{ + [Fact(Skip = "Not yet implemented")] + public void MeleeStrategy_Execute_ReturnsCombatResult() + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler.Tests/InventoryTests.cs b/ClassCrawler.Tests/InventoryTests.cs new file mode 100644 index 0000000..5dac66b --- /dev/null +++ b/ClassCrawler.Tests/InventoryTests.cs @@ -0,0 +1,12 @@ +namespace ClassCrawler.Tests; + +/// Placeholder tests for player inventory management. Students will complete these. +public class InventoryTests +{ + [Fact(Skip = "Not yet implemented")] + public void AddItem_IncreasesInventoryCount() + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler.Tests/ItemTests.cs b/ClassCrawler.Tests/ItemTests.cs new file mode 100644 index 0000000..2dc043d --- /dev/null +++ b/ClassCrawler.Tests/ItemTests.cs @@ -0,0 +1,12 @@ +namespace ClassCrawler.Tests; + +/// Placeholder tests for item usage. Students will complete these. +public class ItemTests +{ + [Fact(Skip = "Not yet implemented")] + public void Potion_Use_RestoresHealth() + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler.Tests/LootResolverTests.cs b/ClassCrawler.Tests/LootResolverTests.cs new file mode 100644 index 0000000..d2e11cc --- /dev/null +++ b/ClassCrawler.Tests/LootResolverTests.cs @@ -0,0 +1,12 @@ +namespace ClassCrawler.Tests; + +/// Placeholder tests for loot resolution. Students will complete these. +public class LootResolverTests +{ + [Fact(Skip = "Not yet implemented")] + public void ResolveLoot_ReturnsItemList() + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler.slnx b/ClassCrawler.slnx new file mode 100644 index 0000000..e33c836 --- /dev/null +++ b/ClassCrawler.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/ClassCrawler/Characters/Character.cs b/ClassCrawler/Characters/Character.cs new file mode 100644 index 0000000..808cf72 --- /dev/null +++ b/ClassCrawler/Characters/Character.cs @@ -0,0 +1,41 @@ +using ClassCrawler.Combat; + +namespace ClassCrawler.Characters; + +/// Abstract base class for all characters in the game. +public abstract class Character +{ + /// The character's display name. + public string Name { get; set; } = string.Empty; + + /// The character's current health points. + public int Health { get; set; } + + /// The character's maximum health points. + public int MaxHealth { get; set; } + + /// The base damage this character deals per attack. + public int AttackPower { get; set; } + + /// Reduces incoming damage by this amount. + public int Defense { get; set; } + + /// The character's current level. + public int Level { get; set; } + + /// Indicates whether the character is still alive. + public bool IsAlive => Health > 0; + + /// The combat strategy used when this character attacks. + public ICombatStrategy? CombatStrategy { get; set; } + + /// Applies damage to this character. + public abstract void TakeDamage(int amount); + + /// Returns a human-readable description of this character. + public virtual string GetDescription() + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler/Characters/Dragon.cs b/ClassCrawler/Characters/Dragon.cs new file mode 100644 index 0000000..104f4a6 --- /dev/null +++ b/ClassCrawler/Characters/Dragon.cs @@ -0,0 +1,32 @@ +namespace ClassCrawler.Characters; + +/// A fearsome dragon — the ultimate dungeon boss. +public class Dragon : Enemy +{ + /// Initialises a Dragon with default starting stats. + public Dragon() + { + Name = "Dragon"; + Health = 200; + MaxHealth = 200; + AttackPower = 35; + Defense = 15; + Level = 10; + ExperienceReward = 200; + GoldReward = 100; + } + + /// Returns a description of the Dragon's attack. + public override string GetAttackDescription() + { + // TODO: Implement + throw new NotImplementedException(); + } + + /// Applies damage to the Dragon, reducing its health. + public override void TakeDamage(int amount) + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler/Characters/Enemy.cs b/ClassCrawler/Characters/Enemy.cs new file mode 100644 index 0000000..35abd3d --- /dev/null +++ b/ClassCrawler/Characters/Enemy.cs @@ -0,0 +1,19 @@ +using ClassCrawler.Items; + +namespace ClassCrawler.Characters; + +/// Abstract base class for all enemy characters. +public abstract class Enemy : Character +{ + /// The amount of experience awarded to the player upon defeating this enemy. + public int ExperienceReward { get; set; } + + /// The amount of gold awarded to the player upon defeating this enemy. + public int GoldReward { get; set; } + + /// Items that may be dropped by this enemy when defeated. + public List LootTable { get; set; } = new(); + + /// Returns a description of this enemy's attack action. + public abstract string GetAttackDescription(); +} diff --git a/ClassCrawler/Characters/Goblin.cs b/ClassCrawler/Characters/Goblin.cs new file mode 100644 index 0000000..2626175 --- /dev/null +++ b/ClassCrawler/Characters/Goblin.cs @@ -0,0 +1,32 @@ +namespace ClassCrawler.Characters; + +/// A weak but cunning enemy found in early dungeon rooms. +public class Goblin : Enemy +{ + /// Initialises a Goblin with default starting stats. + public Goblin() + { + Name = "Goblin"; + Health = 30; + MaxHealth = 30; + AttackPower = 5; + Defense = 2; + Level = 1; + ExperienceReward = 10; + GoldReward = 5; + } + + /// Returns a description of the Goblin's attack. + public override string GetAttackDescription() + { + // TODO: Implement + throw new NotImplementedException(); + } + + /// Applies damage to the Goblin, reducing its health. + public override void TakeDamage(int amount) + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler/Characters/Orc.cs b/ClassCrawler/Characters/Orc.cs new file mode 100644 index 0000000..b09113e --- /dev/null +++ b/ClassCrawler/Characters/Orc.cs @@ -0,0 +1,32 @@ +namespace ClassCrawler.Characters; + +/// A brutish enemy with high health and strong attacks. +public class Orc : Enemy +{ + /// Initialises an Orc with default starting stats. + public Orc() + { + Name = "Orc"; + Health = 60; + MaxHealth = 60; + AttackPower = 12; + Defense = 5; + Level = 3; + ExperienceReward = 25; + GoldReward = 15; + } + + /// Returns a description of the Orc's attack. + public override string GetAttackDescription() + { + // TODO: Implement + throw new NotImplementedException(); + } + + /// Applies damage to the Orc, reducing its health. + public override void TakeDamage(int amount) + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler/Characters/Player.cs b/ClassCrawler/Characters/Player.cs new file mode 100644 index 0000000..8d27005 --- /dev/null +++ b/ClassCrawler/Characters/Player.cs @@ -0,0 +1,51 @@ +using ClassCrawler.Items; + +namespace ClassCrawler.Characters; + +/// Represents the player character controlled by the user. +public class Player : Character +{ + /// The items currently carried by the player. + public List Inventory { get; set; } = new(); + + /// The total experience points earned by the player. + public int Experience { get; set; } + + /// The amount of gold the player is carrying. + public int Gold { get; set; } + + /// Adds an item to the player's inventory. + public void AddItem(Item item) + { + // TODO: Implement + throw new NotImplementedException(); + } + + /// Removes an item from the player's inventory. + public void RemoveItem(Item item) + { + // TODO: Implement + throw new NotImplementedException(); + } + + /// Uses an item from the player's inventory on this player. + public void UseItem(Item item) + { + // TODO: Implement + throw new NotImplementedException(); + } + + /// Adds experience points and checks for level-up. + public void AddExperience(int amount) + { + // TODO: Implement + throw new NotImplementedException(); + } + + /// Applies damage to the player, reducing their health. + public override void TakeDamage(int amount) + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler/ClassCrawler.csproj b/ClassCrawler/ClassCrawler.csproj new file mode 100644 index 0000000..aefb7c8 --- /dev/null +++ b/ClassCrawler/ClassCrawler.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/ClassCrawler/Combat/CharacterFactory.cs b/ClassCrawler/Combat/CharacterFactory.cs new file mode 100644 index 0000000..830e133 --- /dev/null +++ b/ClassCrawler/Combat/CharacterFactory.cs @@ -0,0 +1,21 @@ +using ClassCrawler.Characters; + +namespace ClassCrawler.Combat; + +/// Provides factory methods for creating pre-configured character instances. +public static class CharacterFactory +{ + /// Creates a new Player configured for the given character class. + public static Player CreatePlayer(string name, string characterClass) + { + // TODO: Implement + throw new NotImplementedException(); + } + + /// Creates an Enemy of the specified type scaled to the given level. + public static Enemy CreateEnemy(string type, int scalingLevel) + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler/Combat/CombatEngine.cs b/ClassCrawler/Combat/CombatEngine.cs new file mode 100644 index 0000000..a976caa --- /dev/null +++ b/ClassCrawler/Combat/CombatEngine.cs @@ -0,0 +1,14 @@ +using ClassCrawler.Characters; + +namespace ClassCrawler.Combat; + +/// Orchestrates a full combat encounter between a player and an enemy. +public class CombatEngine +{ + /// Runs a turn-based combat loop until one combatant is defeated. + public CombatOutcome RunCombat(Player player, Enemy enemy) + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler/Combat/CombatOutcome.cs b/ClassCrawler/Combat/CombatOutcome.cs new file mode 100644 index 0000000..0b210c4 --- /dev/null +++ b/ClassCrawler/Combat/CombatOutcome.cs @@ -0,0 +1,4 @@ +namespace ClassCrawler.Combat; + +/// Represents the overall outcome of a completed combat encounter. +public record CombatOutcome(string Winner, int TotalDamageDealt, int ExperienceEarned, int GoldEarned); diff --git a/ClassCrawler/Combat/CombatResult.cs b/ClassCrawler/Combat/CombatResult.cs new file mode 100644 index 0000000..f0c046e --- /dev/null +++ b/ClassCrawler/Combat/CombatResult.cs @@ -0,0 +1,4 @@ +namespace ClassCrawler.Combat; + +/// Represents the result of a single combat action. +public record CombatResult(int DamageDealt, string Description); diff --git a/ClassCrawler/Combat/ICombatStrategy.cs b/ClassCrawler/Combat/ICombatStrategy.cs new file mode 100644 index 0000000..2967cbf --- /dev/null +++ b/ClassCrawler/Combat/ICombatStrategy.cs @@ -0,0 +1,10 @@ +using ClassCrawler.Characters; + +namespace ClassCrawler.Combat; + +/// Defines a strategy for executing a combat action between two characters. +public interface ICombatStrategy +{ + /// Executes the combat action from attacker to target and returns the result. + CombatResult Execute(Character attacker, Character target); +} diff --git a/ClassCrawler/Combat/LootResolver.cs b/ClassCrawler/Combat/LootResolver.cs new file mode 100644 index 0000000..28f38bd --- /dev/null +++ b/ClassCrawler/Combat/LootResolver.cs @@ -0,0 +1,15 @@ +using ClassCrawler.Characters; +using ClassCrawler.Items; + +namespace ClassCrawler.Combat; + +/// Resolves the loot dropped by a defeated enemy. +public class LootResolver +{ + /// Returns the list of items dropped by the given enemy. + public List ResolveLoot(Enemy enemy) + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler/Combat/MagicStrategy.cs b/ClassCrawler/Combat/MagicStrategy.cs new file mode 100644 index 0000000..c07d3d7 --- /dev/null +++ b/ClassCrawler/Combat/MagicStrategy.cs @@ -0,0 +1,14 @@ +using ClassCrawler.Characters; + +namespace ClassCrawler.Combat; + +/// A magic-based combat strategy that uses spells to deal damage. +public class MagicStrategy : ICombatStrategy +{ + /// Executes a magic attack from attacker to target. + public CombatResult Execute(Character attacker, Character target) + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler/Combat/MeleeStrategy.cs b/ClassCrawler/Combat/MeleeStrategy.cs new file mode 100644 index 0000000..e4bcc7d --- /dev/null +++ b/ClassCrawler/Combat/MeleeStrategy.cs @@ -0,0 +1,14 @@ +using ClassCrawler.Characters; + +namespace ClassCrawler.Combat; + +/// A close-range physical combat strategy. +public class MeleeStrategy : ICombatStrategy +{ + /// Executes a melee attack from attacker to target. + public CombatResult Execute(Character attacker, Character target) + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler/Combat/RangedStrategy.cs b/ClassCrawler/Combat/RangedStrategy.cs new file mode 100644 index 0000000..d4788aa --- /dev/null +++ b/ClassCrawler/Combat/RangedStrategy.cs @@ -0,0 +1,14 @@ +using ClassCrawler.Characters; + +namespace ClassCrawler.Combat; + +/// A ranged combat strategy that attacks from a distance. +public class RangedStrategy : ICombatStrategy +{ + /// Executes a ranged attack from attacker to target. + public CombatResult Execute(Character attacker, Character target) + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler/Events/EnemyDefeatedEvent.cs b/ClassCrawler/Events/EnemyDefeatedEvent.cs new file mode 100644 index 0000000..0ec23a1 --- /dev/null +++ b/ClassCrawler/Events/EnemyDefeatedEvent.cs @@ -0,0 +1,9 @@ +namespace ClassCrawler.Events; + +/// Event raised when an enemy is defeated in combat. +public class EnemyDefeatedEvent : GameEvent +{ + /// Initialises an EnemyDefeatedEvent with a default message. + public EnemyDefeatedEvent(string message) + : base(GameEventType.EnemyDefeated, message) { } +} diff --git a/ClassCrawler/Events/GameEvent.cs b/ClassCrawler/Events/GameEvent.cs new file mode 100644 index 0000000..f41e55b --- /dev/null +++ b/ClassCrawler/Events/GameEvent.cs @@ -0,0 +1,18 @@ +namespace ClassCrawler.Events; + +/// Abstract base class for all game events. +public abstract class GameEvent +{ + /// The category of this event. + public GameEventType EventType { get; } + + /// A human-readable message describing what happened. + public string Message { get; } + + /// Initialises a new GameEvent with the given type and message. + protected GameEvent(GameEventType eventType, string message) + { + EventType = eventType; + Message = message; + } +} diff --git a/ClassCrawler/Events/GameEventSystem.cs b/ClassCrawler/Events/GameEventSystem.cs new file mode 100644 index 0000000..02f7b8e --- /dev/null +++ b/ClassCrawler/Events/GameEventSystem.cs @@ -0,0 +1,26 @@ +namespace ClassCrawler.Events; + +/// Global publish/subscribe system for game events. +public static class GameEventSystem +{ + /// Registers a listener to receive future events. + public static void Subscribe(IGameEventListener listener) + { + // TODO: Implement + throw new NotImplementedException(); + } + + /// Removes a previously registered listener. + public static void Unsubscribe(IGameEventListener listener) + { + // TODO: Implement + throw new NotImplementedException(); + } + + /// Broadcasts a game event to all registered listeners. + public static void Publish(GameEvent gameEvent) + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler/Events/GameEventType.cs b/ClassCrawler/Events/GameEventType.cs new file mode 100644 index 0000000..a00545c --- /dev/null +++ b/ClassCrawler/Events/GameEventType.cs @@ -0,0 +1,17 @@ +namespace ClassCrawler.Events; + +/// Categorises the types of events that can occur during gameplay. +public enum GameEventType +{ + /// Fired when the player gains enough experience to level up. + PlayerLevelUp, + + /// Fired when an enemy is defeated in combat. + EnemyDefeated, + + /// Fired when the player picks up an item. + ItemPickedUp, + + /// Fired when the player's health reaches zero. + PlayerDeath +} diff --git a/ClassCrawler/Events/IGameEventListener.cs b/ClassCrawler/Events/IGameEventListener.cs new file mode 100644 index 0000000..6f5d729 --- /dev/null +++ b/ClassCrawler/Events/IGameEventListener.cs @@ -0,0 +1,8 @@ +namespace ClassCrawler.Events; + +/// Defines a listener that can receive and react to game events. +public interface IGameEventListener +{ + /// Called whenever a game event is published to the event system. + void OnEvent(GameEvent gameEvent); +} diff --git a/ClassCrawler/Events/ItemPickedUpEvent.cs b/ClassCrawler/Events/ItemPickedUpEvent.cs new file mode 100644 index 0000000..f13e143 --- /dev/null +++ b/ClassCrawler/Events/ItemPickedUpEvent.cs @@ -0,0 +1,9 @@ +namespace ClassCrawler.Events; + +/// Event raised when the player picks up an item. +public class ItemPickedUpEvent : GameEvent +{ + /// Initialises an ItemPickedUpEvent with a default message. + public ItemPickedUpEvent(string message) + : base(GameEventType.ItemPickedUp, message) { } +} diff --git a/ClassCrawler/Events/PlayerDeathEvent.cs b/ClassCrawler/Events/PlayerDeathEvent.cs new file mode 100644 index 0000000..2b8b124 --- /dev/null +++ b/ClassCrawler/Events/PlayerDeathEvent.cs @@ -0,0 +1,9 @@ +namespace ClassCrawler.Events; + +/// Event raised when the player's health reaches zero. +public class PlayerDeathEvent : GameEvent +{ + /// Initialises a PlayerDeathEvent with a default message. + public PlayerDeathEvent(string message) + : base(GameEventType.PlayerDeath, message) { } +} diff --git a/ClassCrawler/Events/PlayerLevelUpEvent.cs b/ClassCrawler/Events/PlayerLevelUpEvent.cs new file mode 100644 index 0000000..af56362 --- /dev/null +++ b/ClassCrawler/Events/PlayerLevelUpEvent.cs @@ -0,0 +1,9 @@ +namespace ClassCrawler.Events; + +/// Event raised when the player gains a level. +public class PlayerLevelUpEvent : GameEvent +{ + /// Initialises a PlayerLevelUpEvent with a default message. + public PlayerLevelUpEvent(string message) + : base(GameEventType.PlayerLevelUp, message) { } +} diff --git a/ClassCrawler/Items/Armor.cs b/ClassCrawler/Items/Armor.cs new file mode 100644 index 0000000..b1a1552 --- /dev/null +++ b/ClassCrawler/Items/Armor.cs @@ -0,0 +1,17 @@ +using ClassCrawler.Characters; + +namespace ClassCrawler.Items; + +/// Armor that increases the wearer's defense. +public class Armor : Item +{ + /// The bonus added to the character's defense stat. + public int DefenseBonus { get; set; } + + /// Equips this armor, applying its defense bonus to the target. + public override void Use(Character target) + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler/Items/Item.cs b/ClassCrawler/Items/Item.cs new file mode 100644 index 0000000..8385926 --- /dev/null +++ b/ClassCrawler/Items/Item.cs @@ -0,0 +1,19 @@ +using ClassCrawler.Characters; + +namespace ClassCrawler.Items; + +/// Abstract base class for all items in the game. +public abstract class Item +{ + /// The item's display name. + public string Name { get; set; } = string.Empty; + + /// A short description of the item. + public string Description { get; set; } = string.Empty; + + /// The weight of the item in the player's inventory. + public int Weight { get; set; } + + /// Applies this item's effect to the target character. + public abstract void Use(Character target); +} diff --git a/ClassCrawler/Items/Potion.cs b/ClassCrawler/Items/Potion.cs new file mode 100644 index 0000000..4e6635a --- /dev/null +++ b/ClassCrawler/Items/Potion.cs @@ -0,0 +1,17 @@ +using ClassCrawler.Characters; + +namespace ClassCrawler.Items; + +/// A consumable potion that restores health to the target character. +public class Potion : Item +{ + /// The number of health points restored when this potion is used. + public int HealAmount { get; set; } + + /// Drinks this potion, restoring health to the target character. + public override void Use(Character target) + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler/Items/Weapon.cs b/ClassCrawler/Items/Weapon.cs new file mode 100644 index 0000000..4eb09e5 --- /dev/null +++ b/ClassCrawler/Items/Weapon.cs @@ -0,0 +1,17 @@ +using ClassCrawler.Characters; + +namespace ClassCrawler.Items; + +/// A weapon that increases the wielder's attack power. +public class Weapon : Item +{ + /// The bonus damage added to the character's attack power. + public int DamageBonus { get; set; } + + /// Equips this weapon, applying its damage bonus to the target. + public override void Use(Character target) + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler/Persistence/GameState.cs b/ClassCrawler/Persistence/GameState.cs new file mode 100644 index 0000000..4c63944 --- /dev/null +++ b/ClassCrawler/Persistence/GameState.cs @@ -0,0 +1,9 @@ +namespace ClassCrawler.Persistence; + +/// A serializable snapshot of the current game state. +public record GameState( + string PlayerName, + int Health, + int Level, + string CurrentRoomName, + List Inventory); diff --git a/ClassCrawler/Persistence/IGameRepository.cs b/ClassCrawler/Persistence/IGameRepository.cs new file mode 100644 index 0000000..a52d257 --- /dev/null +++ b/ClassCrawler/Persistence/IGameRepository.cs @@ -0,0 +1,17 @@ +namespace ClassCrawler.Persistence; + +/// Defines the contract for persisting and retrieving game data. +public interface IGameRepository +{ + /// Persists the provided game state to storage. + void SaveGame(GameState gameState); + + /// Loads a previously saved game state by player name. + GameState? LoadGame(string playerName); + + /// Records a player's score on the leaderboard. + void SaveScore(string playerName, int score); + + /// Returns all leaderboard entries ordered by score descending. + List GetLeaderboard(); +} diff --git a/ClassCrawler/Persistence/LeaderboardEntry.cs b/ClassCrawler/Persistence/LeaderboardEntry.cs new file mode 100644 index 0000000..8787359 --- /dev/null +++ b/ClassCrawler/Persistence/LeaderboardEntry.cs @@ -0,0 +1,4 @@ +namespace ClassCrawler.Persistence; + +/// A single entry in the game leaderboard. +public record LeaderboardEntry(string PlayerName, int Score, DateTime DateAchieved); diff --git a/ClassCrawler/Persistence/SqliteGameRepository.cs b/ClassCrawler/Persistence/SqliteGameRepository.cs new file mode 100644 index 0000000..b09ab14 --- /dev/null +++ b/ClassCrawler/Persistence/SqliteGameRepository.cs @@ -0,0 +1,33 @@ +namespace ClassCrawler.Persistence; + +/// SQLite-backed implementation of IGameRepository. +public class SqliteGameRepository : IGameRepository +{ + /// Persists the provided game state to the SQLite database. + public void SaveGame(GameState gameState) + { + // TODO: Implement + throw new NotImplementedException(); + } + + /// Loads a previously saved game state from the SQLite database. + public GameState? LoadGame(string playerName) + { + // TODO: Implement + throw new NotImplementedException(); + } + + /// Records a player's score in the SQLite leaderboard table. + public void SaveScore(string playerName, int score) + { + // TODO: Implement + throw new NotImplementedException(); + } + + /// Returns all leaderboard entries from the SQLite database. + public List GetLeaderboard() + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler/Program.cs b/ClassCrawler/Program.cs new file mode 100644 index 0000000..f90caa2 --- /dev/null +++ b/ClassCrawler/Program.cs @@ -0,0 +1,3 @@ +using ClassCrawler.UI; + +GameUI.Run(); diff --git a/ClassCrawler/UI/GameUI.cs b/ClassCrawler/UI/GameUI.cs new file mode 100644 index 0000000..93002de --- /dev/null +++ b/ClassCrawler/UI/GameUI.cs @@ -0,0 +1,42 @@ +namespace ClassCrawler.UI; + +/// Manages all console-based user interaction for the game. +public class GameUI +{ + /// Entry point that starts the game and displays the main menu. + public static void Run() + { + Console.WriteLine("Welcome to ClassCrawler!"); + + bool running = true; + while (running) + { + Console.WriteLine(); + Console.WriteLine("1. New Game"); + Console.WriteLine("2. Load Game"); + Console.WriteLine("3. Leaderboard"); + Console.WriteLine("4. Quit"); + Console.Write("> "); + + string? input = Console.ReadLine(); + switch (input) + { + case "1": + // TODO: Implement New Game + throw new NotImplementedException(); + case "2": + // TODO: Implement Load Game + throw new NotImplementedException(); + case "3": + // TODO: Implement Leaderboard + throw new NotImplementedException(); + case "4": + running = false; + break; + default: + Console.WriteLine("Invalid option. Please try again."); + break; + } + } + } +} diff --git a/ClassCrawler/World/Dungeon.cs b/ClassCrawler/World/Dungeon.cs new file mode 100644 index 0000000..e79001f --- /dev/null +++ b/ClassCrawler/World/Dungeon.cs @@ -0,0 +1,25 @@ +namespace ClassCrawler.World; + +/// Represents the entire dungeon and tracks the player's current location. +public class Dungeon +{ + /// All rooms that make up this dungeon. + public List Rooms { get; set; } = new(); + + /// The room the player is currently in. + public Room? CurrentRoom { get; set; } + + /// Attempts to move the player in the given direction. + public bool MovePlayer(string direction) + { + // TODO: Implement + throw new NotImplementedException(); + } + + /// Creates a small four-room test dungeon and returns it. + public static Dungeon GenerateDungeon() + { + // TODO: Implement + throw new NotImplementedException(); + } +} diff --git a/ClassCrawler/World/Room.cs b/ClassCrawler/World/Room.cs new file mode 100644 index 0000000..b9e71ed --- /dev/null +++ b/ClassCrawler/World/Room.cs @@ -0,0 +1,26 @@ +using ClassCrawler.Characters; +using ClassCrawler.Items; + +namespace ClassCrawler.World; + +/// Represents a single room within the dungeon. +public class Room +{ + /// The name of this room. + public string Name { get; set; } = string.Empty; + + /// A description of this room's appearance. + public string Description { get; set; } = string.Empty; + + /// Enemies currently present in this room. + public List Enemies { get; set; } = new(); + + /// Items left on the floor of this room. + public List Items { get; set; } = new(); + + /// Indicates whether all enemies in this room have been defeated. + public bool IsCleared { get; set; } + + /// Maps compass directions to adjacent rooms. + public Dictionary Exits { get; set; } = new(); +} diff --git a/README.md b/README.md index 79cb922..3ac4c3d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,51 @@ # ClassCrawler -A C# dungeon crawler where good object-oriented design is your best weapon + +> A C# dungeon crawler where good object-oriented design is your best weapon. + +## Overview + +ClassCrawler is a turn-based dungeon crawler built with C# and .NET 8. +It is designed as a **teaching scaffold**: the architecture and interfaces are +fully defined, but the core method bodies are left unimplemented — your job is +to bring the game to life. + +Work through the namespaces listed below, replace every `// TODO: Implement` +and `throw new NotImplementedException()` with working code, and watch the +test suite go green. + +## Getting Started + +```bash +# Clone the repository +git clone https://github.com/Merlissa09/ClassCrawler.git +cd ClassCrawler + +# Run the application +dotnet run --project ClassCrawler +``` + +## Project Structure + +``` +ClassCrawler/ +├── ClassCrawler/ # Main console application +│ ├── Characters/ # Character hierarchy (Player, Enemy, Goblin, Orc, Dragon) +│ ├── Items/ # Item hierarchy (Weapon, Armor, Potion) +│ ├── Combat/ # Combat strategies, engine, factory, loot resolver +│ ├── World/ # Room and Dungeon classes +│ ├── Events/ # Event system (publish / subscribe) +│ ├── Persistence/ # Save / load and leaderboard (SQLite) +│ ├── UI/ # Console UI entry point +│ └── Program.cs +├── ClassCrawler.Tests/ # xUnit test project +│ ├── CombatEngineTests.cs +│ ├── CombatStrategyTests.cs +│ ├── ItemTests.cs +│ ├── InventoryTests.cs +│ └── LootResolverTests.cs +└── ClassCrawler.sln +``` + +## Contributing + + From e3739127a57883074467d99914df14cae6136c82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 21:33:07 +0000 Subject: [PATCH 3/5] Replace .slnx with traditional .sln format as required by spec Co-authored-by: Merlissa09 <41809281+Merlissa09@users.noreply.github.com> --- ClassCrawler.sln | 48 +++++++++++++++++++++++++++++++++++++++++++++++ ClassCrawler.slnx | 4 ---- 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 ClassCrawler.sln delete mode 100644 ClassCrawler.slnx diff --git a/ClassCrawler.sln b/ClassCrawler.sln new file mode 100644 index 0000000..e27a1e5 --- /dev/null +++ b/ClassCrawler.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassCrawler", "ClassCrawler\ClassCrawler.csproj", "{94F8E38A-9D6D-44D1-866F-2461D88DF238}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassCrawler.Tests", "ClassCrawler.Tests\ClassCrawler.Tests.csproj", "{A693E9EC-85B8-418A-9E54-ED5406527BFE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {94F8E38A-9D6D-44D1-866F-2461D88DF238}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94F8E38A-9D6D-44D1-866F-2461D88DF238}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94F8E38A-9D6D-44D1-866F-2461D88DF238}.Debug|x64.ActiveCfg = Debug|Any CPU + {94F8E38A-9D6D-44D1-866F-2461D88DF238}.Debug|x64.Build.0 = Debug|Any CPU + {94F8E38A-9D6D-44D1-866F-2461D88DF238}.Debug|x86.ActiveCfg = Debug|Any CPU + {94F8E38A-9D6D-44D1-866F-2461D88DF238}.Debug|x86.Build.0 = Debug|Any CPU + {94F8E38A-9D6D-44D1-866F-2461D88DF238}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94F8E38A-9D6D-44D1-866F-2461D88DF238}.Release|Any CPU.Build.0 = Release|Any CPU + {94F8E38A-9D6D-44D1-866F-2461D88DF238}.Release|x64.ActiveCfg = Release|Any CPU + {94F8E38A-9D6D-44D1-866F-2461D88DF238}.Release|x64.Build.0 = Release|Any CPU + {94F8E38A-9D6D-44D1-866F-2461D88DF238}.Release|x86.ActiveCfg = Release|Any CPU + {94F8E38A-9D6D-44D1-866F-2461D88DF238}.Release|x86.Build.0 = Release|Any CPU + {A693E9EC-85B8-418A-9E54-ED5406527BFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A693E9EC-85B8-418A-9E54-ED5406527BFE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A693E9EC-85B8-418A-9E54-ED5406527BFE}.Debug|x64.ActiveCfg = Debug|Any CPU + {A693E9EC-85B8-418A-9E54-ED5406527BFE}.Debug|x64.Build.0 = Debug|Any CPU + {A693E9EC-85B8-418A-9E54-ED5406527BFE}.Debug|x86.ActiveCfg = Debug|Any CPU + {A693E9EC-85B8-418A-9E54-ED5406527BFE}.Debug|x86.Build.0 = Debug|Any CPU + {A693E9EC-85B8-418A-9E54-ED5406527BFE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A693E9EC-85B8-418A-9E54-ED5406527BFE}.Release|Any CPU.Build.0 = Release|Any CPU + {A693E9EC-85B8-418A-9E54-ED5406527BFE}.Release|x64.ActiveCfg = Release|Any CPU + {A693E9EC-85B8-418A-9E54-ED5406527BFE}.Release|x64.Build.0 = Release|Any CPU + {A693E9EC-85B8-418A-9E54-ED5406527BFE}.Release|x86.ActiveCfg = Release|Any CPU + {A693E9EC-85B8-418A-9E54-ED5406527BFE}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/ClassCrawler.slnx b/ClassCrawler.slnx deleted file mode 100644 index e33c836..0000000 --- a/ClassCrawler.slnx +++ /dev/null @@ -1,4 +0,0 @@ - - - - From 29eddac609196b543a6e2402a31265c2386dfd8d Mon Sep 17 00:00:00 2001 From: Melissa Hegney <41809281+Merlissa09@users.noreply.github.com> Date: Sat, 14 Mar 2026 17:38:50 -0700 Subject: [PATCH 4/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ClassCrawler.sln | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ClassCrawler.sln b/ClassCrawler.sln index e27a1e5..3637984 100644 --- a/ClassCrawler.sln +++ b/ClassCrawler.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassCrawler", "ClassCrawler\ClassCrawler.csproj", "{94F8E38A-9D6D-44D1-866F-2461D88DF238}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClassCrawler", "ClassCrawler\ClassCrawler.csproj", "{94F8E38A-9D6D-44D1-866F-2461D88DF238}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassCrawler.Tests", "ClassCrawler.Tests\ClassCrawler.Tests.csproj", "{A693E9EC-85B8-418A-9E54-ED5406527BFE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClassCrawler.Tests", "ClassCrawler.Tests\ClassCrawler.Tests.csproj", "{A693E9EC-85B8-418A-9E54-ED5406527BFE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From a180a6ee7892372cd5614bd372e071003a533f33 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Mar 2026 00:39:17 +0000 Subject: [PATCH 5/5] Reduce Program.cs to a single top-level statement Co-authored-by: Merlissa09 <41809281+Merlissa09@users.noreply.github.com> --- ClassCrawler/Program.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ClassCrawler/Program.cs b/ClassCrawler/Program.cs index f90caa2..35aca1c 100644 --- a/ClassCrawler/Program.cs +++ b/ClassCrawler/Program.cs @@ -1,3 +1 @@ -using ClassCrawler.UI; - -GameUI.Run(); +ClassCrawler.UI.GameUI.Run();