It's dangerous to code alone! Take this.

The Game's Status

The following is one possible solution to this challenge.

AttackAction.cs

using System;

/// <summary>
/// An action type that executes an attack on a target.
/// </summary>
public class AttackAction : IAction
{
    // The attack to run.
    private readonly IAttack _attack;

    // The target of the attack.
    private readonly Character _target;

    /// <summary>
    /// Creates a new attack action, capturing the attack and target of the attack.
    /// </summary>
    public AttackAction(IAttack attack, Character target)
    {
        _attack = attack;
        _target = target;
    }

    /// <summary>
    /// Runs the attack action.
    /// </summary>
    public void Run(Battle battle, Character character)
    {
        // Display that the attack is happening.
        Console.WriteLine($"{character.Name} used {_attack.Name} on {_target.Name}.");

        // Get the attack's damage for this specific attack and deal it out to the target.
        AttackData data = _attack.Create();
        _target.HP -= data.Damage;

        // Display that the damage has been dealt and where the character's HP is at now.
        Console.WriteLine($"{_attack.Name} dealt {data.Damage} damage to {_target.Name}.");
        Console.WriteLine($"{_target.Name} is now at {_target.HP}/{_target.MaxHP} HP.");

        // If the target dies because of the attack, remove it from the party and tell the user.
        if (!_target.IsAlive)
        {
            battle.GetPartyFor(_target).Characters.Remove(_target);
            Console.WriteLine($"{_target.Name} was defeated!");
        }
    }
}

Battle.cs

using System;

/// <summary>
/// Represents a single battle in the game.
/// </summary>
public class Battle
{
    /// <summary>
    /// The party of heroes.
    /// </summary>
    public Party Heroes { get; }

    /// <summary>
    /// The party of monsters.
    /// </summary>
    public Party Monsters { get; }

    /// <summary>
    /// Creates a new battle with the two parties involved.
    /// </summary>
    public Battle(Party heroes, Party monsters)
    {
        Heroes = heroes;
        Monsters = monsters;
    }

    /// <summary>
    /// Runs the battle to completion.
    /// </summary>
    public void Run()
    {
        // Run rounds until the outcome is known.
        while (!IsOver)
        {
            // For each character in each party...
            foreach (Party party in new[] { Heroes, Monsters })
            {
                foreach (Character character in party.Characters)
                {
                    Console.WriteLine(); // Slight separation gap.

                    BattleRenderer.Render(this, character);

                    // Display who's turn it is.
                    Console.WriteLine($"{character.Name} is taking a turn...");

                    // Have the player in charge of the party pick an action for the character, and then run that action.
                    party.Player.ChooseAction(this, character).Run(this, character);

                    if (IsOver) break; // If the last action ended the battle, there is no need to go on to other characters.
                }

                if (IsOver) break; // If the last action ended the battle, there is no need to go on to other parties.
            }
        }
    }

    /// <summary>
    /// Indicates whether the game is over or not. This is based on whether a party has no characters left to fight.
    /// </summary>
    public bool IsOver => Heroes.Characters.Count == 0 || Monsters.Characters.Count == 0;

    /// <summary>
    /// Gives you the party that the character is not in. The party that is the enemy of the character in question.
    /// </summary>
    public Party GetEnemyPartyFor(Character character) => Heroes.Characters.Contains(character) ? Monsters : Heroes;

    /// <summary>
    /// Gives you the party that the character is in.
    /// </summary>
    public Party GetPartyFor(Character character) => Heroes.Characters.Contains(character) ? Heroes : Monsters;
}

BattleRenderer.cs

using System;

public static class BattleRenderer
{
    public static void Render(Battle battle, Character activeCharacter)
    {
        // Display the top banner.
        ColoredConsole.WriteLine($"===================================================== BATTLE ====================================================", ConsoleColor.White);

        // Display the heroes and equipped gear.
        foreach (Character character in battle.Heroes.Characters)
        {
            ConsoleColor color = character == activeCharacter ? ConsoleColor.Yellow : ConsoleColor.Gray;
            ColoredConsole.WriteLine($"{character.Name,-45} ({character.HP,3}/{character.MaxHP,-3})", color);
        }

        // Display the middle banner.
        ColoredConsole.WriteLine("------------------------------------------------------ VS -------------------------------------------------------", ConsoleColor.White);

        // Display the monsters and equipped gear.
        foreach (Character character in battle.Monsters.Characters)
        {
            ConsoleColor color = character == activeCharacter ? ConsoleColor.Yellow : ConsoleColor.Gray;
            ColoredConsole.WriteLine($"                                                          {character.Name,45} ({character.HP,3}/{character.MaxHP,-3})", color);
        }

        // Display the bottom banner.
        ColoredConsole.WriteLine("=================================================================================================================", ConsoleColor.White);
    }
}


Character.cs

using System;

/// <summary>
/// Defines what all characters in the game have in common.
/// </summary>
public abstract class Character
{
    /// <summary>
    /// The name of the character.
    /// </summary>
    public abstract string Name { get; }

    /// <summary>
    /// The character's standard attack.
    /// </summary>
    public abstract IAttack StandardAttack { get; }

    // Stores the hit points remaining for the character.
    private int _hp;

    /// <summary>
    /// Gets or sets the current hit points for the character, ensuring it always stays at or above 0 and at or below MaxHP.
    /// </summary>
    public int HP
    {
        get => _hp;
        set => _hp = Math.Clamp(value, 0, MaxHP);
    }

    /// <summary>
    /// The maximum HP that the character has.
    /// </summary>
    public int MaxHP { get; }

    /// <summary>
    /// Indicates if the character is alive or not.
    /// </summary>
    public bool IsAlive => HP > 0;

    /// <summary>
    /// Creates a new character with a specific amount of HP. The character will start with both HP and MaxHP at this level.
    /// </summary>
    public Character(int hp)
    {
        MaxHP = hp;
        HP = hp;
    }
}

ColoredConsole.cs

using System;

/// <summary>
/// A class that provides some convenience methods over the top of the console window for displaying text
/// with color.
/// </summary>
public static class ColoredConsole
{
    /// <summary>
    /// Writes a line of text in a specific color.
    /// </summary>
    public static void WriteLine(string text, ConsoleColor color)
    {
        ConsoleColor previousColor = Console.ForegroundColor;
        Console.ForegroundColor = color;
        Console.WriteLine(text);
        Console.ForegroundColor = previousColor;
    }

    /// <summary>
    /// Writes some text (no new line) in a specific color.
    /// </summary>
    public static void Write(string text, ConsoleColor color)
    {
        ConsoleColor previousColor = Console.ForegroundColor;
        Console.ForegroundColor = color;
        Console.Write(text);
        Console.ForegroundColor = previousColor;
    }

    /// <summary>
    /// Asks the user a question and on the same line, gets a reply back, switching the user's response
    /// to a cyan color so it stands out.
    /// </summary>
    /// <param name="questionToAsk"></param>
    /// <returns></returns>
    public static string Prompt(string questionToAsk)
    {
        ConsoleColor previousColor = Console.ForegroundColor;
        Console.ForegroundColor = ConsoleColor.Gray;
        Console.Write(questionToAsk + " ");
        Console.ForegroundColor = ConsoleColor.Cyan;
        string input = Console.ReadLine();
        Console.ForegroundColor = previousColor;
        return input;
    }
}

ComputerPlayer.cs

using System.Collections.Generic;
using System.Threading;

/// <summary>
/// A simple computer player--an AI. The computer follows a simple set of rules to decide which action to take.
/// </summary>
public class ComputerPlayer : IPlayer
{
    public IAction ChooseAction(Battle battle, Character character)
    {
        // Pretend to think for a bit.
        Thread.Sleep(500);

        // If there's something to attack, attack it with your standard attack.
        List<Character> potentialTargets = battle.GetEnemyPartyFor(character).Characters;
        if (potentialTargets.Count > 0) return new AttackAction(character.StandardAttack, battle.GetEnemyPartyFor(character).Characters[0]);

        // If there's nothing better to do, do nothing.
        return new DoNothingAction();
    }
}

ConsolePlayer.cs

using System;
using System.Collections.Generic;

/// <summary>
/// A player that retrieves commands from the human through the console window.
/// </summary>
public class ConsolePlayer : IPlayer
{
    public IAction ChooseAction(Battle battle, Character character)
    {
        // This uses a menu-based approach. We create the choices from the menu, including their name, whether they are enabled, and
        // what action to pick if they are enabled and chosen.
        // After that, we display the menu and ask the user to make a selection.
        // If the selected option is enabled, use the action associated with it.

        List<MenuChoice> menuChoices = CreateMenuOptions(battle, character);

        for (int index = 0; index < menuChoices.Count; index++)
            ColoredConsole.WriteLine($"{index + 1} - {menuChoices[index].Description}", menuChoices[index].Enabled ? ConsoleColor.Gray : ConsoleColor.DarkGray);

        string choice = ColoredConsole.Prompt("What do you want to do?");
        int menuIndex = Convert.ToInt32(choice) - 1;

        if (menuChoices[menuIndex].Enabled) return menuChoices[menuIndex].Action;

        return new DoNothingAction(); // <-- This is actually fairly unforgiving. Typing in garbage or attempting to use a disabled option results in doing nothing. It would be better to try again. (Maybe that can be done as a Making It Your Own challenge.
    }

    private List<MenuChoice> CreateMenuOptions(Battle battle, Character character)
    {
        Party currentParty = battle.GetPartyFor(character);
        Party otherParty = battle.GetEnemyPartyFor(character);

        List<MenuChoice> menuChoices = new List<MenuChoice>();

        // Add the standard attack as an option.
        if (otherParty.Characters.Count > 0)
            menuChoices.Add(new MenuChoice($"Standard Attack ({character.StandardAttack.Name})", true, new AttackAction(character.StandardAttack, otherParty.Characters[0])));
        else
            menuChoices.Add(new MenuChoice($"Standard Attack ({character.StandardAttack.Name})", false, null));

        // Add doing nothing as an option.
        menuChoices.Add(new MenuChoice("Do Nothing", true, new DoNothingAction()));

        return menuChoices;
    }
}

public record MenuChoice(string Description, bool Enabled, IAction Action);

DoNothingAction.cs

using System;

/// <summary>
/// An action type that does nothing (besides say that the character did nothing).
/// </summary>
public class DoNothingAction : IAction
{
    public void Run(Battle battle, Character actor) => Console.WriteLine($"{actor.Name} did NOTHING.");
}

IAction.cs

/// <summary>
/// Defines what all action possibilities in the system must look like.
/// </summary>
public interface IAction
{
    /// <summary>
    /// Runs the action, giving the action the full context of the battle and the character who is running the action.
    /// If an action needs additional information, then they should typically "request" those by having parameters
    /// for them in their constructors, and save them to fields for use when `Run` is called.
    /// </summary>
    void Run(Battle battle, Character actor);
}


IAttack.cs

/// <summary>
/// Represents an attack that a character might have. Each attack has a name and the ability
/// to produce attack data by request, for when somebody uses the attack.
/// </summary>
public interface IAttack
{
    /// <summary>
    /// The name of the attack.
    /// </summary>
    string Name { get; }

    /// <summary>
    /// Creates new attack data. Called when a character uses an attack.
    /// </summary>
    AttackData Create();
}

/// <summary>
/// The collection of information that defines a specific usage or occurence of an attack.
/// </summary>
public record AttackData(int Damage);

IPlayer.cs

/// <summary>
/// Represents a player--one of entities that control characters and pick actions for them when it is the character's turn.
/// </summary>
public interface IPlayer
{
    /// <summary>
    /// Allows the player to choose an action for a given character. The battle is provided as context, so that it has the
    /// information it needs to make good decisions.
    /// </summary>
    IAction ChooseAction(Battle battle, Character character);
}


Party.cs

using System.Collections.Generic;

/// <summary>
/// Represents a party (of either heroes or monsters). Contains the characters in the party and the player that
/// is running the show for the party.
/// </summary>
public class Party
{
    /// <summary>
    /// The player that is making decisions for this party.
    /// </summary>
    public IPlayer Player { get; set; }

    /// <summary>
    /// The list of characters that are still alive in the party.
    /// </summary>
    public List<Character> Characters { get; } = new List<Character>();

    /// <summary>
    /// The items the party has in their inventory.
    /// </summary>
    public List<IItem> Items { get; } = new List<IItem>();
}


Program.cs

using System;

// Get the name from the player. I've called ToUpper on this because most elements in the game use ALL CAPS to refer to proper nouns. This keeps it consistent.
string name = ColoredConsole.Prompt("What is your name?").ToUpper();

// Let the user pick a gameplay mode and then create players based on the choice they made.
Console.WriteLine("Game Mode Selection:");
Console.WriteLine("1 - Human vs. Computer");
Console.WriteLine("2 - Computer vs. Computer");
Console.WriteLine("3 - Human vs. Human");
string choice = ColoredConsole.Prompt("What mode do you want to use?");

IPlayer player1, player2;

if (choice == "1") { player1 = new ConsolePlayer(); player2 = new ComputerPlayer(); }
else if (choice == "2") { player1 = new ComputerPlayer(); player2 = new ComputerPlayer(); }
else { player1 = new ConsolePlayer(); player2 = new ConsolePlayer(); }

// Construct the hero party. Put Player 1 in charge of this party.
Party heroes = new Party { Player = player1 };
heroes.Characters.Add(new TheTrueProgrammer(name));

// Create all the monster parties now. (We could create one at a time, being able to just iterate over an array was too convenient in this case.)
Party[] monsterParties = new Party[] { CreateMonstersForBattle1(), CreateMonstersForBattle2(), CreateMonstersForBattle3() };

// Loop through all the battles (we're tentatively assuming the hero is going to win them all...
for (int battleNumber = 0; battleNumber < monsterParties.Length; battleNumber++)
{
    // Get the party of monsters for the current battle.
    Party monsters = monsterParties[battleNumber];
    monsters.Player = player2; // Put Player 2 in charge of this party.

    // Create the battle between the two and run it to completion.
    Battle battle = new Battle(heroes, monsters);
    battle.Run();

    // If our assumption was wrong and the heroes all died off, then end the game.
    if (heroes.Characters.Count == 0) break;
}

// Display who won.
if (heroes.Characters.Count > 0) ColoredConsole.WriteLine("You have defeated the Uncoded One's forces! You have won the battle!", ConsoleColor.Green);
else ColoredConsole.WriteLine("You have been defeated. The Uncoded One has won.", ConsoleColor.Red);

// Create the monsters for Battle #1.
Party CreateMonstersForBattle1()
{
    Party monsters = new Party();
    monsters.Characters.Add(new Skeleton());
    return monsters;
}

// Create the monsters for Battle #2.
Party CreateMonstersForBattle2()
{
    Party monsters = new Party();
    monsters.Characters.Add(new Skeleton());
    monsters.Characters.Add(new Skeleton());
    return monsters;
}

// Create the monsters for Battle #3.
Party CreateMonstersForBattle3()
{
    Party monsters = new Party();
    monsters.Characters.Add(new TheUncodedOne());
    return monsters;
}

Skeleton.cs

using System;

/// <summary>
/// A character that represents a skeleton--a simple monster type with a bone crunch attack.
/// </summary>
public class Skeleton : Character
{
    public override string Name => "SKELETON";
    public override IAttack StandardAttack { get; } = new BoneCrunch();

    public Skeleton() : base(5) { }
}

/// <summary>
/// An attack that deals 0 or 1 damage randomly.
/// </summary>
public class BoneCrunch : IAttack
{
    private static readonly Random _random = new Random();

    public string Name => "BONE CRUNCH";
    public AttackData Create() => new AttackData(_random.Next(2));
}

TheTrueProgrammer.cs

/// <summary>
/// The main hero and player character of the game.
/// </summary>
public class TheTrueProgrammer : Character
{
    public override string Name { get; }

    public TheTrueProgrammer(string name) : base(25) => Name = name;
    public override IAttack StandardAttack { get; } = new Punch();
}

/// <summary>
/// Punch is a simple attack that reliably deals 1 damage.
/// </summary>
public class Punch : IAttack
{
    public string Name => "PUNCH";
    public AttackData Create() => new AttackData(1);
}

TheUncodedOne.cs

using System;

/// <summary>
/// The character that represents the big bad evil in the game.
/// </summary>
public class TheUncodedOne : Character
{
    public override string Name => "THE UNCODED ONE";
    public TheUncodedOne() : base(15) { }
    public override IAttack StandardAttack { get; } = new Unraveling();
}

/// <summary>
/// An attack that deals 0 to 2 damage randomly.
/// </summary>
public class Unraveling : IAttack
{
    private static readonly Random _random = new Random();
    public string Name => "UNRAVELING";
    public AttackData Create() => new AttackData(_random.Next(3));
}