Description
In this kata, you'll be working with a basic RPG (Role-Playing Game) combat engine. The system features turn-based combat between different character types, starting with Dwarves and Goblins.
Core Mechanics
- Each character has health, defense, and specific attack abilities
- Damage calculation includes defense reduction and random elements
- Characters can't have negative health
- Characters die when health reaches zero
- Combat is turn-based, with Dwarves attacking first
Character Stats
Dwarf
- Uses AxeAttack: Base damage (axeSkill) + Random(1-5)
- Higher base health but slower attacks
Goblin
- Uses ClawAttack: Base damage (clawStrength) + Random(1-3)
- Lower base health but faster attacks
Technical Details
The engine provides:
- Abstract
Character
class with base health and damage mechanics -
Dwarf
andGoblin
classes with specific attack methods -
CombatManager
for handling combat rounds
Your Task
Complete the combat engine by implementing [specific task here]. Your solution should:
- Handle damage calculations correctly
- Process defense reductions
- Manage character death states
- Follow turn-based combat rules
Examples
Dwarf dwarf = new Dwarf("Thorin", health: 100, defense: 5, axeSkill: 8);
Goblin goblin = new Goblin("Grunk", health: 50, defense: 2, clawStrength: 6);
// Combat example
CombatManager combat = new CombatManager();
combat.ExecuteCombatRound(dwarf, goblin);
Would you like me to:
1. Add more technical details?
2. Include more code examples?
3. Expand the character stats section?
4. Add difficulty indicators?
5. Create specific challenges or variations?
namespace Solution
{
using System;
public abstract class Character
{
public string Name { get; protected set; }
protected int maxHealth;
protected int currentHealth;
protected int defense;
public int CurrentHealth => currentHealth;
public virtual void TakeDamage(int damage, Character attacker)
{
int actualDamage = Math.Max(1, damage - defense);
currentHealth = Math.Max(0, currentHealth - actualDamage);
if (currentHealth == 0)
{
Die();
}
}
public virtual bool IsAlive()
{
return currentHealth > 0;
}
protected virtual void Die()
{
currentHealth = 0;
}
}
public class Dwarf : Character
{
private readonly int axeSkill;
public Dwarf(string name, int health, int defense, int axeSkill)
{
Name = name;
maxHealth = health;
currentHealth = health;
this.defense = defense;
this.axeSkill = axeSkill;
}
public int AxeAttack()
{
return axeSkill + new Random().Next(1, 6);
}
}
public class Goblin : Character
{
private readonly int clawStrength;
public Goblin(string name, int health, int defense, int clawStrength)
{
Name = name;
maxHealth = health;
currentHealth = health;
this.defense = defense;
this.clawStrength = clawStrength;
}
public int ClawAttack()
{
return clawStrength + new Random().Next(1, 4);
}
}
public class CombatManager
{
public void ExecuteCombatRound(Dwarf dwarf, Goblin goblin)
{
if (dwarf.IsAlive())
{
int dwarfDamage = dwarf.AxeAttack();
goblin.TakeDamage(dwarfDamage, dwarf);
}
if (goblin.IsAlive())
{
int goblinDamage = goblin.ClawAttack();
dwarf.TakeDamage(goblinDamage, goblin);
}
}
}
}
using NUnit.Framework;
using System;
[TestFixture]
public class CombatTests
{
private Dwarf dwarf;
private Goblin goblin;
private CombatManager combat;
[SetUp]
public void Setup()
{
// Create fresh instances before each test
dwarf = new Dwarf("TestDwarf", health: 100, defense: 5, axeSkill: 10);
goblin = new Goblin("TestGoblin", health: 50, defense: 2, clawStrength: 6);
combat = new CombatManager();
}
[Test]
public void TestCharacterCreation()
{
Assert.Multiple(() =>
{
// Test dwarf creation
Assert.That(dwarf.Name, Is.EqualTo("TestDwarf"));
Assert.That(dwarf.CurrentHealth, Is.EqualTo(100));
Assert.That(dwarf.IsAlive(), Is.True);
// Test goblin creation
Assert.That(goblin.Name, Is.EqualTo("TestGoblin"));
Assert.That(goblin.CurrentHealth, Is.EqualTo(50));
Assert.That(goblin.IsAlive(), Is.True);
});
}
[Test]
public void TestDamageCalculation()
{
// Create a goblin with no defense for predictable damage
var defenselessGoblin = new Goblin("Weak", 50, 0, 6);
// Set Random seed for predictable damage
// Note: In real tests, you might want to mock Random
Random.Shared.Next(1, 6);
// Dwarf attacks with axeSkill 10 (base damage) + random 1-5
int damage = dwarf.AxeAttack();
Assert.That(damage, Is.GreaterThanOrEqualTo(11)); // minimum damage
Assert.That(damage, Is.LessThanOrEqualTo(15)); // maximum damage
}
[Test]
public void TestDefenseReducesDamage()
{
int initialHealth = goblin.CurrentHealth;
goblin.TakeDamage(10, dwarf);
// With 2 defense, should take 8 damage
Assert.That(goblin.CurrentHealth, Is.EqualTo(initialHealth - 8));
}
[Test]
public void TestMinimumDamageIsOne()
{
// Create high defense character
var toughDwarf = new Dwarf("Tough", 100, 20, 5);
int initialHealth = toughDwarf.CurrentHealth;
// Even with high defense, should take at least 1 damage
toughDwarf.TakeDamage(2, goblin);
Assert.That(toughDwarf.CurrentHealth, Is.EqualTo(initialHealth - 1));
}
[Test]
public void TestDeathAtZeroHealth()
{
// Ensure character dies when health reaches 0
goblin.TakeDamage(100, dwarf); // Overkill damage
Assert.That(goblin.IsAlive(), Is.False);
Assert.That(goblin.CurrentHealth, Is.EqualTo(0));
}
[Test]
public void TestCombatRound()
{
int initialDwarfHealth = dwarf.CurrentHealth;
int initialGoblinHealth = goblin.CurrentHealth;
combat.ExecuteCombatRound(dwarf, goblin);
Assert.Multiple(() =>
{
// Both should have taken damage
Assert.That(dwarf.CurrentHealth, Is.LessThan(initialDwarfHealth));
Assert.That(goblin.CurrentHealth, Is.LessThan(initialGoblinHealth));
});
}
[Test]
public void TestDeadCharacterCantAttack()
{
// Kill the goblin
goblin.TakeDamage(100, dwarf);
int dwarfHealthBeforeRound = dwarf.CurrentHealth;
// Execute combat round with dead goblin
combat.ExecuteCombatRound(dwarf, goblin);
// Dwarf's health should not change since dead goblin can't attack
Assert.That(dwarf.CurrentHealth, Is.EqualTo(dwarfHealthBeforeRound));
}
[Test]
public void TestHealthNeverNegative()
{
// Deal massive damage
goblin.TakeDamage(1000, dwarf);
Assert.That(goblin.CurrentHealth, Is.EqualTo(0));
}
[TestCase(1)]
[TestCase(5)]
[TestCase(10)]
public void TestDifferentDamageLevels(int damage)
{
int initialHealth = goblin.CurrentHealth;
goblin.TakeDamage(damage, dwarf);
Assert.That(goblin.CurrentHealth, Is.LessThan(initialHealth));
}
}