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 + +### +