C# & ASP.NET Topic
The Knight Tournament Simulation In C#
The knight tournament is a turn-based game in which any number of knights can participate. At the beginning of the tournament, each knight receives the same, freely definable number of health points. In each round, every knight performs one attack against a randomly selected opponent. If the attack is successful, the opponent loses health points. When a knight’s health points drop to zero or below, the knight is defeated and eliminated from the tournament. The tournament ends when only one knight remains. The success of an attack is determined by rolling dice: both the attacker and the defender roll a six-sided die. The difference between the attack roll and the defense roll is calculated. If the difference is positive, the attack is successful and the defender loses health points. If the attacker defeats the opponent (meaning the opponent’s health drops to zero or below), the attacker’s die gains one additional side for all future attacks. Develop a simulation (no graphical display) that models the scenario described above. At the end of each round, output statistics showing: which attacker damaged which opponent and by how many health points, the current health points of all knights, and each knight’s current attack strength. At the end of the game, display the top three places and the number of defeated opponents for the first-place knight.
Implementation:
Project Structure
text
knight-tournament/
├── KnightTournament.csproj
├── Program.cs
└── vscode/
└── launch.json
Step-by-Step Setup
1. Open Terminal and Create Project
bash
# Create project directory
mkdir KnightTournament
cd KnightTournament
# Create new console project
dotnet new console
# Create .vscode directory for VS Code settings
mkdir .vscode
2. Create VS Code Launch Configuration
Create .vscode/launch.json:
json
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Knight Tournament",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/bin/Debug/net8.0/KnightTournament.dll",
"args": [],
"cwd": "${workspaceFolder}",
"stopAtEntry": false,
"console": "integratedTerminal"
}
]
}
3. Update KnightTournament.csproj
Replace the content of KnightTournament.csproj with:
xml
Run
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>KnightTournament</RootNamespace>
</PropertyGroup>
</Project>
4. Create Main Program Files
Replace the content of Program.cs with the complete code I provided earlier.
5. Create a Task Runner (Optional)
Create .vscode/tasks.json:
json
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/KnightTournament.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "clean",
"command": "dotnet",
"type": "process",
"args": [
"clean",
"${workspaceFolder}/KnightTournament.csproj"
]
},
{
"label": "run",
"command": "dotnet",
"type": "process",
"args": [
"run",
"--project",
"${workspaceFolder}/KnightTournament.csproj"
]
}
]
}
Complete Implementation with Multiple Files (Better Structure)
For a more organized project, you can split the code into multiple files:
Project Structure with Multiple Files:
text
knight-tournament/
├── KnightTournament.csproj
├── Program.cs
├── Models/
│ ├── Knight.cs
│ └── Dice.cs
├── Core/
│ └── Tournament.cs
└── .vscode/
├── launch.json
└── tasks.json
1. Create Directory Structure
bash
# In the KnightTournament directory
mkdir Models
mkdir Core
2. Create Individual Class Files
Models/Knight.cs:
csharp
namespace KnightTournament.Models
{
public class Knight
{
public int Id { get; }
public string Name { get; }
public int Health { get; private set; }
public int MaxHealth { get; }
public int AttackDieSides { get; private set; }
public int DefeatedOpponents { get; private set; }
public int TotalDamageDealt { get; private set; }
public bool IsAlive => Health > 0;
public Knight(int id, int initialHealth)
{
Id = id;
Name = $"Knight {id}";
Health = initialHealth;
MaxHealth = initialHealth;
AttackDieSides = 6;
DefeatedOpponents = 0;
TotalDamageDealt = 0;
}
public void TakeDamage(int damage)
{
Health -= damage;
if (Health < 0) Health = 0;
}
public void OnDefeatOpponent()
{
DefeatedOpponents++;
AttackDieSides++;
}
public void AddDamageDealt(int damage)
{
TotalDamageDealt += damage;
}
public override string ToString()
{
return $"{Name} (HP: {Health}/{MaxHealth}, Die: d{AttackDieSides}, Defeated: {DefeatedOpponents})";
}
}
}
Models/Dice.cs:
csharp
using System;
namespace KnightTournament.Models
{
public static class Dice
{
private static readonly Random random = new Random();
public static int Roll(int sides)
{
return random.Next(1, sides + 1);
}
}
}
Core/Tournament.cs:
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using KnightTournament.Models;
namespace KnightTournament.Core
{
public class Tournament
{
private List<Knight> knights;
private List<Knight> aliveKnights;
private int roundNumber;
private static readonly Random random = new Random();
public Tournament(int numberOfKnights, int initialHealth)
{
knights = new List<Knight>();
for (int i = 1; i <= numberOfKnights; i++)
{
knights.Add(new Knight(i, initialHealth));
}
aliveKnights = new List<Knight>(knights);
roundNumber = 0;
}
public void RunSimulation()
{
Console.WriteLine("=== KNIGHT TOURNAMENT SIMULATION ===");
Console.WriteLine($"Starting with {aliveKnights.Count} knights\n");
while (aliveKnights.Count > 1)
{
SimulateRound();
DisplayRoundStatistics();
aliveKnights = aliveKnights.Where(k => k.IsAlive).ToList();
roundNumber++;
}
DisplayFinalResults();
}
private void SimulateRound()
{
Console.WriteLine($"\n=== ROUND {roundNumber + 1} ===");
var attackers = new List<Knight>(aliveKnights);
foreach (var attacker in attackers)
{
if (!attacker.IsAlive) continue;
var possibleTargets = aliveKnights
.Where(k => k.Id != attacker.Id && k.IsAlive)
.ToList();
if (possibleTargets.Count == 0) continue;
var defender = possibleTargets[random.Next(possibleTargets.Count)];
int attackRoll = Dice.Roll(attacker.AttackDieSides);
int defenseRoll = Dice.Roll(6);
int damage = Math.Max(0, attackRoll - defenseRoll);
if (damage > 0)
{
defender.TakeDamage(damage);
attacker.AddDamageDealt(damage);
Console.WriteLine($"{attacker.Name} attacks {defender.Name}");
Console.WriteLine($" Attack roll (d{attacker.AttackDieSides}): {attackRoll}");
Console.WriteLine($" Defense roll (d6): {defenseRoll}");
Console.WriteLine($" Damage dealt: {damage}");
if (!defender.IsAlive)
{
attacker.OnDefeatOpponent();
Console.WriteLine($" {defender.Name} has been defeated!");
Console.WriteLine($" {attacker.Name}'s attack die increases to d{attacker.AttackDieSides}");
}
}
else
{
Console.WriteLine($"{attacker.Name} attacks {defender.Name}");
Console.WriteLine($" Attack roll (d{attacker.AttackDieSides}): {attackRoll}");
Console.WriteLine($" Defense roll (d6): {defenseRoll}");
Console.WriteLine($" Attack blocked! No damage dealt.");
}
}
}
private void DisplayRoundStatistics()
{
Console.WriteLine("\n--- Round Statistics ---");
Console.WriteLine("Knights' Status:");
foreach (var knight in knights)
{
string status = knight.IsAlive ? "Alive" : "Defeated";
Console.WriteLine($" {knight.Name}: HP {knight.Health}/{knight.MaxHealth}, " +
$"Die: d{knight.AttackDieSides}, " +
$"Defeated: {knight.DefeatedOpponents}, " +
$"Status: {status}");
}
Console.WriteLine($"Knights remaining: {aliveKnights.Count}");
}
private void DisplayFinalResults()
{
Console.WriteLine("\n=== TOURNAMENT COMPLETE ===");
Console.WriteLine($"Tournament lasted {roundNumber} rounds");
var rankedKnights = knights
.OrderByDescending(k => k.IsAlive)
.ThenByDescending(k => k.Health)
.ThenByDescending(k => k.DefeatedOpponents)
.ThenByDescending(k => k.TotalDamageDealt)
.ToList();
Console.WriteLine("\n=== FINAL RANKINGS ===");
for (int i = 0; i < Math.Min(3, rankedKnights.Count); i++)
{
var knight = rankedKnights[i];
string place = i == 0 ? "1st" : i == 1 ? "2nd" : "3rd";
string status = knight.IsAlive ? "CHAMPION!" : "Defeated";
Console.WriteLine($"{place} Place: {knight.Name}");
Console.WriteLine($" Health: {knight.Health}/{knight.MaxHealth}");
Console.WriteLine($" Attack Die: d{knight.AttackDieSides}");
Console.WriteLine($" Opponents Defeated: {knight.DefeatedOpponents}");
Console.WriteLine($" Total Damage Dealt: {knight.TotalDamageDealt}");
Console.WriteLine($" Status: {status}\n");
}
var winner = rankedKnights.First();
Console.WriteLine($"=== TOURNAMENT CHAMPION ===");
Console.WriteLine($"{winner.Name} has won the tournament!");
Console.WriteLine($"Total opponents defeated: {winner.DefeatedOpponents}");
}
}
}
Program.cs:
csharp
using System;
using KnightTournament.Core;
namespace KnightTournament
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("KNIGHT TOURNAMENT SIMULATOR\n");
Console.Write("Enter number of knights (default 10): ");
string knightsInput = Console.ReadLine();
int numberOfKnights = string.IsNullOrEmpty(knightsInput) ? 10 : int.Parse(knightsInput);
Console.Write("Enter initial health points (default 50): ");
string healthInput = Console.ReadLine();
int initialHealth = string.IsNullOrEmpty(healthInput) ? 50 : int.Parse(healthInput);
Tournament tournament = new Tournament(numberOfKnights, initialHealth);
tournament.RunSimulation();
Console.WriteLine("\nPress any key to exit...");
Console.ReadKey();
}
}
}
Build and Run Commands
bash
# Navigate to your project folder
cd KnightTournament
# Build the project
dotnet build
# Run the project
dotnet run
# Or clean and rebuild
dotnet clean
dotnet build
dotnet run
# To run with specific arguments (if you modify the program to accept them)
dotnet run -- --knights 15 --health 100
VS Code Shortcuts
-
Open VS Code in the project:
bash
code .
-
Press F5 to run with debugger
-
Ctrl+Shift+B to build
-
Ctrl+` to open terminal
This structure gives you a professional C# project setup that's easy to navigate and extend in Visual Studio Code.
Key Features Implemented:
1. Core Mechanics:
-
Random dice rolls with variable die sides
-
Damage calculation:
max(0, attackRoll - defenseRoll) -
Knight elimination when HP ≤ 0
-
Attack die sides increase after defeating an opponent
2. Statistics Tracking:
-
Per-round statistics showing attacks and results
-
Current HP and status for all knights
-
Attack strength (die sides) for each knight
-
Defeated opponents count
-
Total damage dealt
3. Final Rankings:
-
Top 3 places displayed
-
Champion's defeated opponents count shown
-
Multiple tie-breakers (health, defeats, damage)
4. Object-Oriented Design:
-
Knightclass with encapsulated state -
Tournamentclass managing game logic -
Diceutility class for random rolls
5. User Customization:
-
Configurable number of knights
-
Configurable starting health points
Sample Output Structure:
text
=== ROUND 1 ===
Knight 1 attacks Knight 3
Attack roll (d6): 5
Defense roll (d6): 2
Damage dealt: 3
--- Round Statistics ---
Knights' Status:
Knight 1: HP 50/50, Die: d6, Defeated: 0, Status: Alive
Knight 2: HP 47/50, Die: d6, Defeated: 0, Status: Alive
...
=== FINAL RANKINGS ===
1st Place: Knight 7
Health: 12/50
Attack Die: d9
Opponents Defeated: 3
Total Damage Dealt: 47
Status: CHAMPION!
Comments (0)
No comments yet. Be the first to comment!