Идея проста:
- У каждого персонажа есть характеристики: здоровье (0–100), атака (0–50), защита (0–50), скорость (0–25), удача (0–1)
- Бой пошаговый:
Атакующий рассчитывает базовый урон ()Код: Выделить всё
AttackNormal - Защитник определяет результат () → полная блокировка/частичная блокировка/сбой
Код: Выделить всё
Defend
Вот моя текущая реализация:
//character.cs
Код: Выделить всё
namespace FightingGame;
public enum DefenseResultType
{
FullBlock,
PartialBlock,
Failed
}
public class DefenseResult(DefenseResultType resultType, int finalDamage)
{
public DefenseResultType ResultType { get; set; } = resultType;
public int FinalDamage { get; set; } = finalDamage;
}
public class Character(string name, int health, int attack, int defense, int speed, Ability? ability, float luck)
{
private string _name = name;
private int _health = health;
private int _attack = attack;
private int _defense = defense;
private int _speed = speed;
private Ability? _ability = ability;
private float _luck = luck;
public string GetName()
{
return _name;
}
public int GetHealth()
{
return _health;
}
public int GetAttack()
{
return _attack;
}
public int GetDefense()
{
return _defense;
}
public int GetSpeed()
{
return _speed;
}
public Ability GetAbility()
{
return _ability;
}
public float GetLuck()
{
return _luck;
}
public void SetName(string name)
{
if(!string.IsNullOrWhiteSpace(name) && name.Length > 1) _name = name;
}
public void SetHealth(int health)
{
if(health >= 0 && health 0 && attack 0 && defense 0 && speed = 0 && luck in future multiple dices can be implemented
int dice = 6;
// dice roll * 5 + (defense + speed/2)
int defenseRoll = Random.Shared.Next(1, dice + 1) * 10 + (int)(defense + Math.Floor((float)speed / 2));
// (attack + speed/2) * luck
int attackPressure = (int)(attackerAttack + Math.Floor((float)attackerSpeed / 2) * attackerLuck);
// full block
if (defenseRoll >= attackPressure + 30)
{
return (DefenseResultType.FullBlock, 0);
}
// partial block
if(defenseRoll >= attackPressure + 15)
{
return (DefenseResultType.PartialBlock, incomingDamage / 3);
}
// failed block
return (DefenseResultType.Failed, incomingDamage);
}
public void TakeDamage(int damage)
{
if (damage > 0)
{
_health = Math.Clamp(_health - damage, 0, 100);
}
}
}
//program.cs:
Код: Выделить всё
namespace FightingGame;
class Program
{
public static void Main(string[] args)
{
Character knight = new Character("Knight", 100, 28, 36, 10, null, 0.20f);
Character assassin = new Character("Assassin", 100, 32, 14, 24, null, 0.42f);
Character guardian = new Character("Guardian", 100, 22, 44, 8, null, 0.10f);
Character berserker = new Character("Berserker", 100, 41, 12, 14, null, 0.25f);
Character duelist = new Character("Duelist", 100, 30, 24, 18, null, 0.30f);
Character scout = new Character("Scout", 100, 24, 16, 25, null, 0.38f);
Console.WriteLine("=== CHARACTER STATS ===");
PrintStats(knight);
PrintStats(assassin);
PrintStats(guardian);
PrintStats(berserker);
PrintStats(duelist);
PrintStats(scout);
Console.WriteLine("\n=== FIGHTS ===\n");
SimulateFight(knight, assassin);
SimulateFight(guardian, berserker);
SimulateFight(duelist, scout);
SimulateFight(knight, berserker);
}
private static void PrintStats(Character character)
{
Console.WriteLine(
$"{character.GetName(),-10} | HP: {character.GetHealth(),3} | ATK: {character.GetAttack(),2} | DEF: {character.GetDefense(),2} | SPD: {character.GetSpeed(),2} | LUCK: {character.GetLuck():0.00}"
);
}
private static void SimulateFight(Character firstTemplate, Character secondTemplate)
{
Character fighterA = CloneCharacter(firstTemplate);
Character fighterB = CloneCharacter(secondTemplate);
Console.WriteLine("==================================================");
Console.WriteLine($"FIGHT: {fighterA.GetName()} vs {fighterB.GetName()}");
Console.WriteLine("==================================================");
PrintStats(fighterA);
PrintStats(fighterB);
Console.WriteLine();
Character attacker;
Character defender;
if (fighterA.GetSpeed() > fighterB.GetSpeed())
{
attacker = fighterA;
defender = fighterB;
}
else if (fighterB.GetSpeed() > fighterA.GetSpeed())
{
attacker = fighterB;
defender = fighterA;
}
else
{
attacker = Random.Shared.Next(0, 2) == 0 ? fighterA : fighterB;
defender = attacker == fighterA ? fighterB : fighterA;
}
Console.WriteLine($"Attacks first: {attacker.GetName()}");
Console.WriteLine();
int round = 1;
int maxRounds = 30;
while (fighterA.GetHealth() > 0 && fighterB.GetHealth() > 0 && round fighterB.GetHealth())
{
Console.WriteLine($"WINNER: {fighterA.GetName()}");
}
else if (fighterB.GetHealth() > fighterA.GetHealth())
{
Console.WriteLine($"WINNER: {fighterB.GetName()}");
}
else
{
Console.WriteLine("DRAW");
}
Console.WriteLine();
}
private static void ExecuteTurn(Character attacker, Character defender)
{
Console.WriteLine($"{attacker.GetName()} attacks {defender.GetName()}");
int baseDamage = attacker.AttackNormal(defender);
var (defenseResult, finalDamage) = defender.Defend(attacker, baseDamage);
Console.WriteLine($"Base damage: {baseDamage}");
switch (defenseResult)
{
case DefenseResultType.FullBlock:
Console.WriteLine($"{defender.GetName()} does FULL BLOCK.");
break;
case DefenseResultType.PartialBlock:
Console.WriteLine($"{defender.GetName()} does PARTIAL BLOCK.");
break;
case DefenseResultType.Failed:
Console.WriteLine($"{defender.GetName()} didn't defend.");
break;
}
Console.WriteLine($"Final damage: {finalDamage}");
defender.TakeDamage(finalDamage);
Console.WriteLine(
$"HP after round -> {attacker.GetName()}: {attacker.GetHealth()} | {defender.GetName()}: {defender.GetHealth()}"
);
Console.WriteLine();
}
private static Character CloneCharacter(Character original)
{
return new Character(
original.GetName(),
original.GetHealth(),
original.GetAttack(),
original.GetDefense(),
original.GetSpeed(),
original.GetAbility(),
original.GetLuck()
);
}
}
- Правильно ли я структурирую обязанности между Атакой и Защитой?
- Уместна ли такая логика внутри класса Символ?
- Есть ли нарушения лучших практик C#? Последний раз я использовал C# некоторое время назад и не знаю, все ли правильно помню.
- Предложения по тому, как сделать код более удобным в сопровождении или расширяемым - я хочу добавить к персонажам способности, например какую-то специальную атаку, какой подход будет лучшим?
- Общие мысли о читабельности и дизайне (не только математика/баланс)
Заранее спасибо!