diff --git a/game-sessions/Features/Sessions/Controllers/CommandersController.cs b/game-sessions/Features/Sessions/Controllers/CommandersController.cs
new file mode 100644
index 0000000..06a3203
--- /dev/null
+++ b/game-sessions/Features/Sessions/Controllers/CommandersController.cs
@@ -0,0 +1,299 @@
+namespace game_sessions.Features.Sessions.Controllers;
+
+using Microsoft.AspNetCore.Mvc;
+using game_sessions.Features.Sessions.Models;
+using game_sessions.Features.Sessions.Services;
+
+///
+/// Controller for managing commanders
+/// SOLID Principle: Single Responsibility Principle (SRP)
+///
+[ApiController]
+[Route("api/[controller]")]
+public class TotoCommandersController : ControllerBase
+{
+ private readonly ITotoCommanderService _commanderService;
+
+ public TotoCommandersController(ITotoCommanderService commanderService)
+ {
+ _commanderService = commanderService;
+ }
+
+ ///
+ /// Retrieves all commanders
+ ///
+ /// List of all commanders
+ [HttpGet]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task>> GetAllCommanders()
+ {
+ var commanders = await _commanderService.GetAllCommandersAsync();
+ return Ok(commanders);
+ }
+
+ ///
+ /// Retrieves a commander by its ID
+ ///
+ /// The commander identifier
+ /// The corresponding commander
+ [HttpGet("{id}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task> GetCommanderById(int id)
+ {
+ var commander = await _commanderService.GetCommanderByIdAsync(id);
+ if (commander == null)
+ {
+ return NotFound($"Commander with ID {id} not found.");
+ }
+ return Ok(commander);
+ }
+
+ ///
+ /// Searches for commanders by name
+ ///
+ /// The name or partial name of the commander
+ /// List of matching commanders
+ [HttpGet("search/{name}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task>> SearchCommandersByName(string name)
+ {
+ var commanders = await _commanderService.SearchCommandersByNameAsync(name);
+ return Ok(commanders);
+ }
+
+ ///
+ /// Filters commanders by color identity (exact match)
+ ///
+ /// The color identity (comma-separated, e.g., "Red,Blue")
+ /// List of commanders with matching color identity
+ [HttpGet("color-identity")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task>> GetCommandersByColorIdentity([FromQuery] string colors)
+ {
+ var colorList = colors.Split(',')
+ .Select(c => Enum.Parse(c.Trim(), true))
+ .ToList();
+
+ var commanders = await _commanderService.GetCommandersByColorIdentityAsync(colorList);
+ return Ok(commanders);
+ }
+
+ ///
+ /// Filters commanders by single color (contains)
+ ///
+ /// The mana color
+ /// List of commanders containing this color in their identity
+ [HttpGet("color/{color}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task>> GetCommandersByColor(ManaColor color)
+ {
+ var commanders = await _commanderService.GetCommandersByColorAsync(color);
+ return Ok(commanders);
+ }
+
+ ///
+ /// Filters commanders by type
+ ///
+ /// The creature type (e.g., "Dragon", "Elf")
+ /// List of commanders of the specified type
+ [HttpGet("type/{type}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task>> GetCommandersByType(string type)
+ {
+ var commanders = await _commanderService.GetCommandersByTypeAsync(type);
+ return Ok(commanders);
+ }
+
+ ///
+ /// Filters commanders by minimum power
+ ///
+ /// The minimum power
+ /// List of commanders with at least this power
+ [HttpGet("power/{minPower}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task>> GetCommandersByMinPower(int minPower)
+ {
+ var commanders = await _commanderService.GetCommandersByMinPowerAsync(minPower);
+ return Ok(commanders);
+ }
+
+ ///
+ /// Filters commanders by minimum toughness
+ ///
+ /// The minimum toughness
+ /// List of commanders with at least this toughness
+ [HttpGet("toughness/{minToughness}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task>> GetCommandersByMinToughness(int minToughness)
+ {
+ var commanders = await _commanderService.GetCommandersByMinToughnessAsync(minToughness);
+ return Ok(commanders);
+ }
+
+ ///
+ /// Retrieves all commanders with partner ability
+ ///
+ /// List of partner commanders
+ [HttpGet("partner")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task>> GetPartnerCommanders()
+ {
+ var commanders = await _commanderService.GetPartnerCommandersAsync();
+ return Ok(commanders);
+ }
+
+ ///
+ /// Filters commanders by ability
+ ///
+ /// The ability to search for
+ /// List of commanders with this ability
+ [HttpGet("ability/{ability}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task>> GetCommandersByAbility(CreatureAbility ability)
+ {
+ var commanders = await _commanderService.GetCommandersByAbilityAsync(ability);
+ return Ok(commanders);
+ }
+
+ ///
+ /// Adds a new commander
+ ///
+ /// The commander to add
+ /// The added commander with its ID
+ [HttpPost]
+ [ProducesResponseType(StatusCodes.Status201Created)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ public async Task> AddCommander([FromBody] TotoCommander commander)
+ {
+ if (string.IsNullOrWhiteSpace(commander.Name))
+ {
+ return BadRequest("The commander name is required.");
+ }
+
+ var addedCommander = await _commanderService.AddCommanderAsync(commander);
+ return CreatedAtAction(nameof(GetCommanderById), new { id = addedCommander.Id }, addedCommander);
+ }
+
+ ///
+ /// Updates an existing commander
+ ///
+ /// The commander identifier
+ /// The updated data
+ /// The result of the operation
+ [HttpPut("{id}")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task UpdateCommander(int id, [FromBody] TotoCommander commander)
+ {
+ var success = await _commanderService.UpdateCommanderAsync(id, commander);
+ if (!success)
+ {
+ return NotFound($"Commander with ID {id} not found.");
+ }
+ return NoContent();
+ }
+
+ ///
+ /// Deletes a commander
+ ///
+ /// The commander identifier
+ /// The result of the operation
+ [HttpDelete("{id}")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task DeleteCommander(int id)
+ {
+ var success = await _commanderService.DeleteCommanderAsync(id);
+ if (!success)
+ {
+ return NotFound($"Commander with ID {id} not found.");
+ }
+ return NoContent();
+ }
+
+ ///
+ /// Calculates the commander tax for a commander
+ ///
+ /// The commander identifier
+ /// The additional mana cost due to commander tax
+ [HttpGet("{id}/tax")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task> CalculateCommanderTax(int id)
+ {
+ var commander = await _commanderService.GetCommanderByIdAsync(id);
+ if (commander == null)
+ {
+ return NotFound($"Commander with ID {id} not found.");
+ }
+
+ var tax = await _commanderService.CalculateCommanderTaxAsync(id);
+ return Ok(new { CommanderId = id, CommanderTax = tax });
+ }
+
+ ///
+ /// Records commander damage dealt to a player
+ ///
+ /// The commander identifier
+ /// The target player identifier
+ /// The amount of damage dealt
+ /// The result of the operation
+ [HttpPost("{id}/damage")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task RecordCommanderDamage(int id, [FromQuery] int playerId, [FromQuery] int damage)
+ {
+ var success = await _commanderService.RecordCommanderDamageAsync(id, playerId, damage);
+ if (!success)
+ {
+ return NotFound($"Commander with ID {id} not found.");
+ }
+
+ var totalDamage = await _commanderService.GetCommanderDamageToPlayerAsync(id, playerId);
+ return Ok(new { CommanderId = id, PlayerId = playerId, DamageDealt = damage, TotalDamage = totalDamage });
+ }
+
+ ///
+ /// Gets total commander damage dealt to a player
+ ///
+ /// The commander identifier
+ /// The target player identifier
+ /// Total damage dealt
+ [HttpGet("{id}/damage/{playerId}")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task> GetCommanderDamageToPlayer(int id, int playerId)
+ {
+ var commander = await _commanderService.GetCommanderByIdAsync(id);
+ if (commander == null)
+ {
+ return NotFound($"Commander with ID {id} not found.");
+ }
+
+ var totalDamage = await _commanderService.GetCommanderDamageToPlayerAsync(id, playerId);
+ return Ok(new { CommanderId = id, PlayerId = playerId, TotalDamage = totalDamage });
+ }
+
+ ///
+ /// Moves a commander to a different zone
+ ///
+ /// The commander identifier
+ /// The destination zone
+ /// The result of the operation
+ [HttpPost("{id}/move")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task MoveCommanderToZone(int id, [FromQuery] CommanderZone zone)
+ {
+ var success = await _commanderService.MoveCommanderToZoneAsync(id, zone);
+ if (!success)
+ {
+ return NotFound($"Commander with ID {id} not found.");
+ }
+
+ var commander = await _commanderService.GetCommanderByIdAsync(id);
+ return Ok(new { CommanderId = id, CurrentZone = commander!.CurrentZone, CommanderTaxCount = commander.CommanderTaxCount });
+ }
+}
+
diff --git a/game-sessions/Features/Sessions/Models/Commander.cs b/game-sessions/Features/Sessions/Models/Commander.cs
new file mode 100644
index 0000000..e48267a
--- /dev/null
+++ b/game-sessions/Features/Sessions/Models/Commander.cs
@@ -0,0 +1,135 @@
+namespace game_sessions.Features.Sessions.Models;
+
+///
+/// Represents a Commander card in Magic: The Gathering
+/// SOLID Principle: Single Responsibility Principle (SRP)
+/// A Commander is a legendary creature that serves as the commander of a deck
+///
+public class TotoCommander
+{
+ ///
+ /// Gets or sets the unique identifier of the commander
+ ///
+ public int Id { get; set; }
+
+ ///
+ /// Gets or sets the name of the commander
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the mana cost of the commander (e.g., "2UU", "1RG")
+ ///
+ public string ManaCost { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the converted mana cost (CMC)
+ ///
+ public int ConvertedManaCost { get; set; }
+
+ ///
+ /// Gets or sets the creature types (e.g., "Human Wizard", "Dragon", "Elf Warrior")
+ ///
+ public List CreatureTypes { get; set; } = new();
+
+ ///
+ /// Gets or sets the color identity of the commander (determines deck building restrictions)
+ ///
+ public List ColorIdentity { get; set; } = new();
+
+ ///
+ /// Gets or sets the actual colors of the commander
+ ///
+ public List Colors { get; set; } = new();
+
+ ///
+ /// Gets or sets the power of the commander
+ ///
+ public int Power { get; set; }
+
+ ///
+ /// Gets or sets the toughness of the commander
+ ///
+ public int Toughness { get; set; }
+
+ ///
+ /// Gets or sets the abilities of the commander (Flying, Trample, Haste, etc.)
+ ///
+ public List Abilities { get; set; } = new();
+
+ ///
+ /// Gets or sets the oracle text (effect of the commander)
+ ///
+ public string OracleText { get; set; } = string.Empty;
+
+ ///
+ /// Gets or sets the flavor text (lore)
+ ///
+ public string? FlavorText { get; set; }
+
+ ///
+ /// Gets or sets the artist name
+ ///
+ public string? Artist { get; set; }
+
+ ///
+ /// Gets or sets the rarity of the card
+ ///
+ public CardRarity Rarity { get; set; }
+
+ ///
+ /// Gets or sets the set code (expansion origin)
+ ///
+ public string? SetCode { get; set; }
+
+ ///
+ /// Gets or sets whether the commander is a partner (can have two commanders)
+ ///
+ public bool IsPartner { get; set; }
+
+ ///
+ /// Gets or sets whether the commander has "Partner with" (specific partner)
+ ///
+ public string? PartnerWith { get; set; }
+
+ ///
+ /// Gets or sets the number of times this commander has been cast from command zone
+ ///
+ public int CommanderTaxCount { get; set; }
+
+ ///
+ /// Gets or sets the total commander damage dealt to each opponent (player ID -> damage)
+ ///
+ public Dictionary CommanderDamage { get; set; } = new();
+
+ ///
+ /// Gets or sets the current zone where the commander is (CommandZone, Battlefield, Graveyard, etc.)
+ ///
+ public CommanderZone CurrentZone { get; set; } = CommanderZone.CommandZone;
+
+ ///
+ /// Gets or sets the owner of the commander
+ ///
+ public int? OwnerId { get; set; }
+}
+
+///
+/// Zone enumeration specific to commanders
+///
+public enum CommanderZone
+{
+ /// The command zone (default position)
+ CommandZone,
+ /// On the battlefield
+ Battlefield,
+ /// In the graveyard
+ Graveyard,
+ /// In exile
+ Exile,
+ /// In the library (deck)
+ Library,
+ /// In hand
+ Hand
+}
+
+
diff --git a/game-sessions/Features/Sessions/Services/CommanderService.cs b/game-sessions/Features/Sessions/Services/CommanderService.cs
new file mode 100644
index 0000000..9223935
--- /dev/null
+++ b/game-sessions/Features/Sessions/Services/CommanderService.cs
@@ -0,0 +1,347 @@
+namespace game_sessions.Features.Sessions.Services;
+
+using game_sessions.Features.Sessions.Models;
+
+///
+/// Commander management service
+/// SOLID Principle: Single Responsibility Principle (SRP)
+/// In-memory implementation for demonstration purposes
+///
+public class TotoCommanderService : ITotoCommanderService
+{
+ private readonly List _commanders;
+ private int _nextId;
+
+ public TotoCommanderService()
+ {
+ _commanders = new List();
+ _nextId = 1;
+
+ // Initialize with sample commanders
+ InitializeSampleCommanders();
+ }
+
+ public Task> GetAllCommandersAsync()
+ {
+ return Task.FromResult>(_commanders);
+ }
+
+ public Task GetCommanderByIdAsync(int id)
+ {
+ var commander = _commanders.FirstOrDefault(c => c.Id == id);
+ return Task.FromResult(commander);
+ }
+
+ public Task> SearchCommandersByNameAsync(string name)
+ {
+ var results = _commanders
+ .Where(c => c.Name.Contains(name, StringComparison.OrdinalIgnoreCase))
+ .ToList();
+ return Task.FromResult>(results);
+ }
+
+ public Task> GetCommandersByColorIdentityAsync(List colors)
+ {
+ var results = _commanders
+ .Where(c => c.ColorIdentity.OrderBy(x => x).SequenceEqual(colors.OrderBy(x => x)))
+ .ToList();
+ return Task.FromResult>(results);
+ }
+
+ public Task> GetCommandersByColorAsync(ManaColor color)
+ {
+ var results = _commanders
+ .Where(c => c.ColorIdentity.Contains(color))
+ .ToList();
+ return Task.FromResult>(results);
+ }
+
+ public Task> GetCommandersByTypeAsync(string creatureType)
+ {
+ var results = _commanders
+ .Where(c => c.CreatureTypes.Any(t =>
+ t.Equals(creatureType, StringComparison.OrdinalIgnoreCase)))
+ .ToList();
+ return Task.FromResult>(results);
+ }
+
+ public Task> GetCommandersByMinPowerAsync(int minPower)
+ {
+ var results = _commanders
+ .Where(c => c.Power >= minPower)
+ .ToList();
+ return Task.FromResult>(results);
+ }
+
+ public Task> GetCommandersByMinToughnessAsync(int minToughness)
+ {
+ var results = _commanders
+ .Where(c => c.Toughness >= minToughness)
+ .ToList();
+ return Task.FromResult>(results);
+ }
+
+ public Task> GetPartnerCommandersAsync()
+ {
+ var results = _commanders
+ .Where(c => c.IsPartner || !string.IsNullOrEmpty(c.PartnerWith))
+ .ToList();
+ return Task.FromResult>(results);
+ }
+
+ public Task> GetCommandersByAbilityAsync(CreatureAbility ability)
+ {
+ var results = _commanders
+ .Where(c => c.Abilities.Contains(ability))
+ .ToList();
+ return Task.FromResult>(results);
+ }
+
+ public Task AddCommanderAsync(TotoCommander commander)
+ {
+ commander.Id = _nextId++;
+ _commanders.Add(commander);
+ return Task.FromResult(commander);
+ }
+
+ public Task UpdateCommanderAsync(int id, TotoCommander commander)
+ {
+ var existingCommander = _commanders.FirstOrDefault(c => c.Id == id);
+ if (existingCommander == null)
+ {
+ return Task.FromResult(false);
+ }
+
+ existingCommander.Name = commander.Name;
+ existingCommander.ManaCost = commander.ManaCost;
+ existingCommander.ConvertedManaCost = commander.ConvertedManaCost;
+ existingCommander.CreatureTypes = commander.CreatureTypes;
+ existingCommander.ColorIdentity = commander.ColorIdentity;
+ existingCommander.Colors = commander.Colors;
+ existingCommander.Power = commander.Power;
+ existingCommander.Toughness = commander.Toughness;
+ existingCommander.Abilities = commander.Abilities;
+ existingCommander.OracleText = commander.OracleText;
+ existingCommander.FlavorText = commander.FlavorText;
+ existingCommander.Artist = commander.Artist;
+ existingCommander.Rarity = commander.Rarity;
+ existingCommander.SetCode = commander.SetCode;
+ existingCommander.IsPartner = commander.IsPartner;
+ existingCommander.PartnerWith = commander.PartnerWith;
+ existingCommander.CommanderTaxCount = commander.CommanderTaxCount;
+ existingCommander.CommanderDamage = commander.CommanderDamage;
+ existingCommander.CurrentZone = commander.CurrentZone;
+ existingCommander.OwnerId = commander.OwnerId;
+
+ return Task.FromResult(true);
+ }
+
+ public Task DeleteCommanderAsync(int id)
+ {
+ var commander = _commanders.FirstOrDefault(c => c.Id == id);
+ if (commander == null)
+ {
+ return Task.FromResult(false);
+ }
+
+ _commanders.Remove(commander);
+ return Task.FromResult(true);
+ }
+
+ public async Task CalculateCommanderTaxAsync(int id)
+ {
+ var commander = await GetCommanderByIdAsync(id);
+ if (commander == null)
+ {
+ return 0;
+ }
+
+ // Commander tax is 2 additional mana for each time it was cast from command zone
+ return commander.CommanderTaxCount * 2;
+ }
+
+ public async Task RecordCommanderDamageAsync(int commanderId, int playerId, int damage)
+ {
+ var commander = await GetCommanderByIdAsync(commanderId);
+ if (commander == null)
+ {
+ return false;
+ }
+
+ if (commander.CommanderDamage.ContainsKey(playerId))
+ {
+ commander.CommanderDamage[playerId] += damage;
+ }
+ else
+ {
+ commander.CommanderDamage[playerId] = damage;
+ }
+
+ return true;
+ }
+
+ public async Task GetCommanderDamageToPlayerAsync(int commanderId, int playerId)
+ {
+ var commander = await GetCommanderByIdAsync(commanderId);
+ if (commander == null)
+ {
+ return 0;
+ }
+
+ return commander.CommanderDamage.GetValueOrDefault(playerId, 0);
+ }
+
+ public async Task MoveCommanderToZoneAsync(int commanderId, CommanderZone newZone)
+ {
+ var commander = await GetCommanderByIdAsync(commanderId);
+ if (commander == null)
+ {
+ return false;
+ }
+
+ var oldZone = commander.CurrentZone;
+ commander.CurrentZone = newZone;
+
+ // If commander is being cast from command zone, increment tax counter
+ if (oldZone == CommanderZone.CommandZone && newZone == CommanderZone.Battlefield)
+ {
+ commander.CommanderTaxCount++;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Initializes sample commanders for demonstration
+ ///
+ private void InitializeSampleCommanders()
+ {
+ // Atraxa, Praetors' Voice
+ _commanders.Add(new TotoCommander
+ {
+ Id = _nextId++,
+ Name = "Atraxa, Praetors' Voice",
+ ManaCost = "GWUB",
+ ConvertedManaCost = 4,
+ CreatureTypes = new List { "Phyrexian", "Angel", "Horror" },
+ ColorIdentity = new List { ManaColor.White, ManaColor.Blue, ManaColor.Black, ManaColor.Green },
+ Colors = new List { ManaColor.White, ManaColor.Blue, ManaColor.Black, ManaColor.Green },
+ Power = 4,
+ Toughness = 4,
+ Abilities = new List { CreatureAbility.Flying, CreatureAbility.Vigilance, CreatureAbility.Deathtouch, CreatureAbility.Lifelink },
+ OracleText = "Flying, vigilance, deathtouch, lifelink. At the beginning of your end step, proliferate.",
+ Rarity = CardRarity.MythicRare,
+ SetCode = "C16",
+ IsPartner = false,
+ CommanderTaxCount = 0,
+ CurrentZone = CommanderZone.CommandZone
+ });
+
+ // Muldrotha, the Gravetide
+ _commanders.Add(new TotoCommander
+ {
+ Id = _nextId++,
+ Name = "Muldrotha, the Gravetide",
+ ManaCost = "3BUG",
+ ConvertedManaCost = 6,
+ CreatureTypes = new List { "Elemental", "Avatar" },
+ ColorIdentity = new List { ManaColor.Blue, ManaColor.Black, ManaColor.Green },
+ Colors = new List { ManaColor.Blue, ManaColor.Black, ManaColor.Green },
+ Power = 6,
+ Toughness = 6,
+ Abilities = new List(),
+ OracleText = "During each of your turns, you may play up to one permanent card of each permanent type from your graveyard.",
+ Rarity = CardRarity.MythicRare,
+ SetCode = "DOM",
+ IsPartner = false,
+ CommanderTaxCount = 0,
+ CurrentZone = CommanderZone.CommandZone
+ });
+
+ // Thrasios, Triton Hero (Partner)
+ _commanders.Add(new TotoCommander
+ {
+ Id = _nextId++,
+ Name = "Thrasios, Triton Hero",
+ ManaCost = "GU",
+ ConvertedManaCost = 2,
+ CreatureTypes = new List { "Merfolk", "Wizard" },
+ ColorIdentity = new List { ManaColor.Green, ManaColor.Blue },
+ Colors = new List { ManaColor.Green, ManaColor.Blue },
+ Power = 1,
+ Toughness = 3,
+ Abilities = new List(),
+ OracleText = "{4}: Scry 1, then reveal the top card of your library. If it's a land card, put it onto the battlefield tapped. Otherwise, draw a card. Partner",
+ Rarity = CardRarity.Rare,
+ SetCode = "C16",
+ IsPartner = true,
+ CommanderTaxCount = 0,
+ CurrentZone = CommanderZone.CommandZone
+ });
+
+ // Edgar Markov
+ _commanders.Add(new TotoCommander
+ {
+ Id = _nextId++,
+ Name = "Edgar Markov",
+ ManaCost = "3RWB",
+ ConvertedManaCost = 6,
+ CreatureTypes = new List { "Vampire", "Knight" },
+ ColorIdentity = new List { ManaColor.Red, ManaColor.White, ManaColor.Black },
+ Colors = new List { ManaColor.Red, ManaColor.White, ManaColor.Black },
+ Power = 4,
+ Toughness = 4,
+ Abilities = new List { CreatureAbility.Haste },
+ OracleText = "Eminence — Whenever you cast another Vampire spell, if Edgar Markov is in the command zone or on the battlefield, create a 1/1 black Vampire creature token. First strike. Whenever Edgar Markov attacks, put a +1/+1 counter on each Vampire you control.",
+ Rarity = CardRarity.MythicRare,
+ SetCode = "C17",
+ IsPartner = false,
+ CommanderTaxCount = 0,
+ CurrentZone = CommanderZone.CommandZone
+ });
+
+ // Kenrith, the Returned King
+ _commanders.Add(new TotoCommander
+ {
+ Id = _nextId++,
+ Name = "Kenrith, the Returned King",
+ ManaCost = "4W",
+ ConvertedManaCost = 5,
+ CreatureTypes = new List { "Human", "Noble" },
+ ColorIdentity = new List { ManaColor.White, ManaColor.Blue, ManaColor.Black, ManaColor.Red, ManaColor.Green },
+ Colors = new List { ManaColor.White },
+ Power = 5,
+ Toughness = 5,
+ Abilities = new List(),
+ OracleText = "{R}: All creatures gain trample and haste until end of turn. {1}{G}: Put a +1/+1 counter on target creature. {2}{W}: Target player gains 5 life. {3}{U}: Target player draws a card. {4}{B}: Put target creature card from a graveyard onto the battlefield under its owner's control.",
+ Rarity = CardRarity.MythicRare,
+ SetCode = "ELD",
+ IsPartner = false,
+ CommanderTaxCount = 0,
+ CurrentZone = CommanderZone.CommandZone
+ });
+
+ // Tymna the Weaver (Partner)
+ _commanders.Add(new TotoCommander
+ {
+ Id = _nextId++,
+ Name = "Tymna the Weaver",
+ ManaCost = "1WB",
+ ConvertedManaCost = 3,
+ CreatureTypes = new List { "Human", "Cleric" },
+ ColorIdentity = new List { ManaColor.White, ManaColor.Black },
+ Colors = new List { ManaColor.White, ManaColor.Black },
+ Power = 2,
+ Toughness = 2,
+ Abilities = new List { CreatureAbility.Lifelink },
+ OracleText = "Lifelink. At the beginning of your postcombat main phase, you may pay X life, where X is the number of opponents that were dealt combat damage this turn. If you do, draw X cards. Partner",
+ Rarity = CardRarity.Rare,
+ SetCode = "C16",
+ IsPartner = true,
+ CommanderTaxCount = 0,
+ CurrentZone = CommanderZone.CommandZone
+ });
+ }
+}
+
+
diff --git a/game-sessions/Features/Sessions/Services/ICommanderService.cs b/game-sessions/Features/Sessions/Services/ICommanderService.cs
new file mode 100644
index 0000000..5716ff3
--- /dev/null
+++ b/game-sessions/Features/Sessions/Services/ICommanderService.cs
@@ -0,0 +1,134 @@
+namespace game_sessions.Features.Sessions.Services;
+
+using game_sessions.Features.Sessions.Models;
+
+///
+/// Interface for the commander management service
+/// SOLID Principles: Interface Segregation Principle (ISP) & Dependency Inversion Principle (DIP)
+///
+public interface ITotoCommanderService
+{
+ ///
+ /// Retrieves all commanders
+ ///
+ /// List of all commanders
+ Task> GetAllCommandersAsync();
+
+ ///
+ /// Retrieves a commander by its ID
+ ///
+ /// The commander identifier
+ /// The corresponding commander or null
+ Task GetCommanderByIdAsync(int id);
+
+ ///
+ /// Searches for commanders by name
+ ///
+ /// The name or partial name of the commander
+ /// List of matching commanders
+ Task> SearchCommandersByNameAsync(string name);
+
+ ///
+ /// Filters commanders by color identity
+ ///
+ /// The color identity (can be multiple colors)
+ /// List of commanders with matching color identity
+ Task> GetCommandersByColorIdentityAsync(List colors);
+
+ ///
+ /// Filters commanders by single color
+ ///
+ /// The mana color
+ /// List of commanders containing this color in their identity
+ Task> GetCommandersByColorAsync(ManaColor color);
+
+ ///
+ /// Filters commanders by type
+ ///
+ /// The creature type (e.g., "Dragon", "Elf")
+ /// List of commanders of the specified type
+ Task> GetCommandersByTypeAsync(string creatureType);
+
+ ///
+ /// Filters commanders by minimum power
+ ///
+ /// The minimum power
+ /// List of commanders with at least this power
+ Task> GetCommandersByMinPowerAsync(int minPower);
+
+ ///
+ /// Filters commanders by minimum toughness
+ ///
+ /// The minimum toughness
+ /// List of commanders with at least this toughness
+ Task> GetCommandersByMinToughnessAsync(int minToughness);
+
+ ///
+ /// Filters commanders with partner ability
+ ///
+ /// List of commanders with partner
+ Task> GetPartnerCommandersAsync();
+
+ ///
+ /// Filters commanders by ability
+ ///
+ /// The ability to search for
+ /// List of commanders with this ability
+ Task> GetCommandersByAbilityAsync(CreatureAbility ability);
+
+ ///
+ /// Adds a new commander
+ ///
+ /// The commander to add
+ /// The added commander with its ID
+ Task AddCommanderAsync(TotoCommander commander);
+
+ ///
+ /// Updates an existing commander
+ ///
+ /// The commander identifier
+ /// The updated data
+ /// True if the update succeeded, false otherwise
+ Task UpdateCommanderAsync(int id, TotoCommander commander);
+
+ ///
+ /// Deletes a commander
+ ///
+ /// The commander identifier
+ /// True if the deletion succeeded, false otherwise
+ Task DeleteCommanderAsync(int id);
+
+ ///
+ /// Calculates the total commander tax for a commander
+ ///
+ /// The commander identifier
+ /// The additional mana cost (2 per previous cast)
+ Task CalculateCommanderTaxAsync(int id);
+
+ ///
+ /// Records commander damage dealt to a player
+ ///
+ /// The commander identifier
+ /// The target player identifier
+ /// The amount of damage dealt
+ /// True if recorded successfully
+ Task RecordCommanderDamageAsync(int commanderId, int playerId, int damage);
+
+ ///
+ /// Gets total commander damage dealt to a specific player
+ ///
+ /// The commander identifier
+ /// The target player identifier
+ /// Total damage dealt
+ Task GetCommanderDamageToPlayerAsync(int commanderId, int playerId);
+
+ ///
+ /// Moves a commander to a different zone
+ ///
+ /// The commander identifier
+ /// The destination zone
+ /// True if moved successfully
+ Task MoveCommanderToZoneAsync(int commanderId, CommanderZone newZone);
+}
+
+
diff --git a/game-sessions/Program.cs b/game-sessions/Program.cs
index 21f5456..82535a1 100644
--- a/game-sessions/Program.cs
+++ b/game-sessions/Program.cs
@@ -13,6 +13,8 @@
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
var app = builder.Build();
diff --git a/game-sessions/commanders.http b/game-sessions/commanders.http
new file mode 100644
index 0000000..45fc57f
--- /dev/null
+++ b/game-sessions/commanders.http
@@ -0,0 +1,313 @@
+### Commander API Tests
+### Base URL
+@baseUrl = https://localhost:7189
+@commanderId = 1
+@playerId = 1
+
+### 1. Get All Commanders
+GET {{baseUrl}}/api/TotoCommanders
+Accept: application/json
+
+###
+
+### 2. Get Commander by ID
+GET {{baseUrl}}/api/TotoCommanders/{{commanderId}}
+Accept: application/json
+
+###
+
+### 3. Search Commanders by Name
+GET {{baseUrl}}/api/TotoCommanders/search/Atraxa
+Accept: application/json
+
+###
+
+### 4. Get Commanders by Color Identity (exact match - GWUB)
+GET {{baseUrl}}/api/TotoCommanders/color-identity?colors=Green,White,Blue,Black
+Accept: application/json
+
+###
+
+### 5. Get Commanders by Color Identity (UBG)
+GET {{baseUrl}}/api/TotoCommanders/color-identity?colors=Blue,Black,Green
+Accept: application/json
+
+###
+
+### 6. Get Commanders by Single Color (Blue)
+GET {{baseUrl}}/api/TotoCommanders/color/Blue
+Accept: application/json
+
+###
+
+### 7. Get Commanders by Single Color (Green)
+GET {{baseUrl}}/api/TotoCommanders/color/Green
+Accept: application/json
+
+###
+
+### 8. Get Commanders by Type (Vampire)
+GET {{baseUrl}}/api/TotoCommanders/type/Vampire
+Accept: application/json
+
+###
+
+### 9. Get Commanders by Type (Human)
+GET {{baseUrl}}/api/TotoCommanders/type/Human
+Accept: application/json
+
+###
+
+### 10. Get Commanders by Minimum Power (4)
+GET {{baseUrl}}/api/TotoCommanders/power/4
+Accept: application/json
+
+###
+
+### 11. Get Commanders by Minimum Power (6)
+GET {{baseUrl}}/api/TotoCommanders/power/6
+Accept: application/json
+
+###
+
+### 12. Get Commanders by Minimum Toughness (4)
+GET {{baseUrl}}/api/TotoCommanders/toughness/4
+Accept: application/json
+
+###
+
+### 13. Get Partner Commanders
+GET {{baseUrl}}/api/TotoCommanders/partner
+Accept: application/json
+
+###
+
+### 14. Get Commanders by Ability (Flying)
+GET {{baseUrl}}/api/TotoCommanders/ability/Flying
+Accept: application/json
+
+###
+
+### 15. Get Commanders by Ability (Lifelink)
+GET {{baseUrl}}/api/TotoCommanders/ability/Lifelink
+Accept: application/json
+
+###
+
+### 16. Add New Commander
+POST {{baseUrl}}/api/TotoCommanders
+Content-Type: application/json
+
+{
+ "name": "Sliver Overlord",
+ "manaCost": "WUBRG",
+ "convertedManaCost": 5,
+ "creatureTypes": ["Sliver"],
+ "colorIdentity": ["White", "Blue", "Black", "Red", "Green"],
+ "colors": ["White", "Blue", "Black", "Red", "Green"],
+ "power": 7,
+ "toughness": 7,
+ "abilities": [],
+ "oracleText": "{3}: Search your library for a Sliver card, reveal that card, put it into your hand, then shuffle. {3}: Gain control of target Sliver.",
+ "rarity": "MythicRare",
+ "setCode": "SCG",
+ "isPartner": false,
+ "commanderTaxCount": 0,
+ "currentZone": "CommandZone"
+}
+
+###
+
+### 17. Add Another Commander (Partner)
+POST {{baseUrl}}/api/TotoCommanders
+Content-Type: application/json
+
+{
+ "name": "Sakashima of a Thousand Faces",
+ "manaCost": "3U",
+ "convertedManaCost": 4,
+ "creatureTypes": ["Human", "Rogue"],
+ "colorIdentity": ["Blue"],
+ "colors": ["Blue"],
+ "power": 3,
+ "toughness": 1,
+ "abilities": [],
+ "oracleText": "You may have Sakashima of a Thousand Faces enter the battlefield as a copy of another creature you control, except it has Sakashima of a Thousand Faces's other abilities. Partner",
+ "rarity": "Rare",
+ "setCode": "CMR",
+ "isPartner": true,
+ "commanderTaxCount": 0,
+ "currentZone": "CommandZone"
+}
+
+###
+
+### 18. Update Commander
+PUT {{baseUrl}}/api/TotoCommanders/1
+Content-Type: application/json
+
+{
+ "name": "Atraxa, Praetors' Voice",
+ "manaCost": "GWUB",
+ "convertedManaCost": 4,
+ "creatureTypes": ["Phyrexian", "Angel", "Horror"],
+ "colorIdentity": ["White", "Blue", "Black", "Green"],
+ "colors": ["White", "Blue", "Black", "Green"],
+ "power": 4,
+ "toughness": 4,
+ "abilities": ["Flying", "Vigilance", "Deathtouch", "Lifelink"],
+ "oracleText": "Flying, vigilance, deathtouch, lifelink. At the beginning of your end step, proliferate.",
+ "flavorText": "Her purpose is clear: evolution through infection.",
+ "rarity": "MythicRare",
+ "setCode": "C16",
+ "isPartner": false,
+ "commanderTaxCount": 0,
+ "currentZone": "CommandZone",
+ "ownerId": 1
+}
+
+###
+
+### 19. Calculate Commander Tax (before casting)
+GET {{baseUrl}}/api/TotoCommanders/1/tax
+Accept: application/json
+
+###
+
+### 20. Move Commander to Battlefield (first cast)
+POST {{baseUrl}}/api/TotoCommanders/1/move?zone=Battlefield
+Content-Type: application/json
+
+###
+
+### 21. Calculate Commander Tax (after first cast)
+GET {{baseUrl}}/api/TotoCommanders/1/tax
+Accept: application/json
+
+###
+
+### 22. Move Commander to Graveyard
+POST {{baseUrl}}/api/TotoCommanders/1/move?zone=Graveyard
+Content-Type: application/json
+
+###
+
+### 23. Move Commander back to Command Zone
+POST {{baseUrl}}/api/TotoCommanders/1/move?zone=CommandZone
+Content-Type: application/json
+
+###
+
+### 24. Move Commander to Battlefield (second cast - tax applies)
+POST {{baseUrl}}/api/TotoCommanders/1/move?zone=Battlefield
+Content-Type: application/json
+
+###
+
+### 25. Calculate Commander Tax (after second cast - should be 2)
+GET {{baseUrl}}/api/TotoCommanders/1/tax
+Accept: application/json
+
+###
+
+### 26. Record Commander Damage (5 damage to player 1)
+POST {{baseUrl}}/api/TotoCommanders/1/damage?playerId=1&damage=5
+Content-Type: application/json
+
+###
+
+### 27. Record More Commander Damage (8 damage to player 1)
+POST {{baseUrl}}/api/TotoCommanders/1/damage?playerId=1&damage=8
+Content-Type: application/json
+
+###
+
+### 28. Get Commander Damage to Player 1 (should be 13 total)
+GET {{baseUrl}}/api/TotoCommanders/1/damage/1
+Accept: application/json
+
+###
+
+### 29. Record Commander Damage to Player 2 (7 damage)
+POST {{baseUrl}}/api/TotoCommanders/1/damage?playerId=2&damage=7
+Content-Type: application/json
+
+###
+
+### 30. Get Commander Damage to Player 2
+GET {{baseUrl}}/api/TotoCommanders/1/damage/2
+Accept: application/json
+
+###
+
+### 31. Record Commander Damage to Player 2 (14 more damage - lethal at 21)
+POST {{baseUrl}}/api/TotoCommanders/1/damage?playerId=2&damage=14
+Content-Type: application/json
+
+###
+
+### 32. Delete Commander
+DELETE {{baseUrl}}/api/TotoCommanders/7
+Accept: application/json
+
+###
+
+### 33. Try to Get Deleted Commander (should return 404)
+GET {{baseUrl}}/api/TotoCommanders/7
+Accept: application/json
+
+###
+
+### 34. Add Commander with Invalid Data (missing name - should fail)
+POST {{baseUrl}}/api/TotoCommanders
+Content-Type: application/json
+
+{
+ "name": "",
+ "manaCost": "2UU",
+ "convertedManaCost": 4,
+ "creatureTypes": ["Wizard"],
+ "colorIdentity": ["Blue"],
+ "colors": ["Blue"],
+ "power": 2,
+ "toughness": 2
+}
+
+###
+
+### 35. Search Non-Existent Commander
+GET {{baseUrl}}/api/TotoCommanders/search/NonExistentCommander
+Accept: application/json
+
+###
+
+### 36. Get Commanders by Color Identity (Red,White,Black - Edgar Markov)
+GET {{baseUrl}}/api/TotoCommanders/color-identity?colors=Red,White,Black
+Accept: application/json
+
+###
+
+### 37. Move Commander to Exile
+POST {{baseUrl}}/api/TotoCommanders/2/move?zone=Exile
+Content-Type: application/json
+
+###
+
+### 38. Move Commander to Library
+POST {{baseUrl}}/api/TotoCommanders/3/move?zone=Library
+Content-Type: application/json
+
+###
+
+### 39. Move Commander to Hand
+POST {{baseUrl}}/api/TotoCommanders/4/move?zone=Hand
+Content-Type: application/json
+
+###
+
+### 40. Get All Commanders to See Final State
+GET {{baseUrl}}/api/TotoCommanders
+Accept: application/json
+
+###
+